Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cw-abc: Updated hatch phase mechanics, donations, queries #699

Merged
merged 16 commits into from
Apr 25, 2023
1,844 changes: 1,614 additions & 230 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 22 additions & 2 deletions contracts/external/cw-abc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cw-abc"
version = "0.0.1"
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>", "Jake Hartnell"]
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>", "Jake Hartnell", "Adair <adairrr@users.noreply.github.com>"]
edition = { workspace = true }
description = "Implements an Augmented Bonding Curve"
license = "Apache-2.0"
Expand All @@ -16,6 +16,7 @@ crate-type = ["cdylib", "rlib"]
backtraces = ["cosmwasm-std/backtraces"]
# use library feature to disable all instantiate/execute/query exports
library = []
boot = ["dep:boot-core"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️


[dependencies]
cw-utils = { workspace = true }
Expand All @@ -30,6 +31,25 @@ integer-cbrt = "0.1.2"
token-bindings = { git = "https://github.com/CosmosContracts/token-bindings", rev = "1412b94" }
cw-address-like = "1.0.4"
cw-ownable = { workspace = true }
cw-paginate = { workspace = true }
boot-core = { version = "0.10.0", optional = true, git = "https://github.com/AbstractSDK/BOOT", branch = "fix/custom_binding_contract_wrapper" }

[dev-dependencies]
speculoos = "0.11.0"
speculoos = "0.11.0"
#cw-multi-test = { version = "0.16.0" }
anyhow = { workspace = true }
cw-abc = { path = ".", features = ["boot"] }




[profile.release]
rpath = false
lto = true
overflow-checks = true
opt-level = 3
debug = false
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
161 changes: 87 additions & 74 deletions contracts/external/cw-abc/src/abc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Api, Decimal as StdDecimal, ensure, StdResult, Uint128};
use cw_address_like::AddressLike;
use token_bindings::Metadata;
use crate::curves::{Constant, Curve, decimal, DecimalPlaces, Linear, SquareRoot};
use cosmwasm_std::{ensure, Decimal as StdDecimal, Uint128};

use crate::curves::{decimal, Constant, Curve, DecimalPlaces, Linear, SquareRoot};
use crate::ContractError;
use token_bindings::Metadata;

#[cw_serde]
pub struct SupplyToken {
Expand Down Expand Up @@ -34,9 +33,7 @@ pub struct MinMax {
}

#[cw_serde]
pub struct HatchConfig<T: AddressLike> {
// Initial contributors (Hatchers) allow list
pub allowlist: Option<Vec<T>>,
pub struct HatchConfig {
// /// TODO: The minimum and maximum contribution amounts (min, max) in the reserve token
// pub contribution_limits: MinMax,
// The initial raise range (min, max) in the reserve token
Expand All @@ -46,90 +43,70 @@ pub struct HatchConfig<T: AddressLike> {
pub initial_price: Uint128,
// The initial allocation (θ), percentage of the initial raise allocated to the Funding Pool
pub initial_allocation_ratio: StdDecimal,
// Exit tax for the hatch phase
pub exit_tax: StdDecimal,
}

impl From<HatchConfig<Addr>> for HatchConfig<String> {
fn from(value: HatchConfig<Addr>) -> Self {
HatchConfig {
allowlist: value.allowlist.map(|addresses| {
addresses.into_iter().map(|addr| addr.to_string()).collect()
}),
initial_raise: value.initial_raise,
initial_price: value.initial_price,
initial_allocation_ratio: value.initial_allocation_ratio,
}
}
}


impl HatchConfig<String> {
impl HatchConfig {
/// Validate the hatch config
pub fn validate(&self, api: &dyn Api) -> Result<HatchConfig<Addr>, ContractError> {
pub fn validate(&self) -> Result<(), ContractError> {
ensure!(
self.initial_raise.min < self.initial_raise.max,
ContractError::HatchPhaseConfigError("Initial raise minimum value must be less than maximum value.".to_string())
ContractError::HatchPhaseConfigError(
"Initial raise minimum value must be less than maximum value.".to_string()
)
);

ensure!(
!self.initial_price.is_zero(),
ContractError::HatchPhaseConfigError("Initial price must be greater than zero.".to_string())
ContractError::HatchPhaseConfigError(
"Initial price must be greater than zero.".to_string()
)
);

// TODO: define better values
ensure!(
self.initial_allocation_ratio <= StdDecimal::percent(100u64),
ContractError::HatchPhaseConfigError("Initial allocation percentage must be between 0 and 100.".to_string())
ContractError::HatchPhaseConfigError(
"Initial allocation percentage must be between 0 and 100.".to_string()
)
);

let allowlist = self
.allowlist
.as_ref()
.map(|addresses| {
addresses
.iter()
.map(|addr| api.addr_validate(addr))
.collect::<StdResult<Vec<_>>>()
})
.transpose()?;

Ok(HatchConfig {
allowlist,
initial_raise: self.initial_raise.clone(),
initial_price: self.initial_price,
initial_allocation_ratio: self.initial_allocation_ratio,
})
}
}

impl HatchConfig<Addr> {
/// Check if the sender is allowlisted for the hatch phase
pub fn assert_allowlisted(&self, hatcher: &Addr) -> Result<(), ContractError> {
if let Some(allowlist) = &self.allowlist {
ensure!(
allowlist.contains(hatcher),
ContractError::SenderNotAllowlisted {
sender: hatcher.to_string(),
}
);
}
// TODO: define better values
ensure!(
self.exit_tax <= StdDecimal::percent(100u64),
ContractError::HatchPhaseConfigError(
"Exit taxation percentage must be between 0 and 100.".to_string()
)
);

Ok(())
}
}


#[cw_serde]
pub struct OpenConfig {
// Percentage of capital put into the Reserve Pool during the Open phase
pub allocation_percentage: StdDecimal,
// Exit taxation ratio
pub exit_tax: StdDecimal,
}

impl OpenConfig {
/// Validate the open config
pub fn validate(&self) -> Result<(), ContractError> {

ensure!(
self.allocation_percentage <= StdDecimal::percent(100u64),
ContractError::OpenPhaseConfigError("Reserve percentage must be between 0 and 100.".to_string())
ContractError::OpenPhaseConfigError(
"Reserve percentage must be between 0 and 100.".to_string()
)
);

ensure!(
self.exit_tax <= StdDecimal::percent(100u64),
ContractError::OpenPhaseConfigError(
"Exit taxation percentage must be between 0 and 100.".to_string()
)
);

Ok(())
Expand All @@ -139,11 +116,17 @@ impl OpenConfig {
#[cw_serde]
pub struct ClosedConfig {}

impl ClosedConfig {
/// Validate the closed config
pub fn validate(&self) -> Result<(), ContractError> {
Ok(())
}
}

#[cw_serde]
pub struct CommonsPhaseConfig<T: AddressLike> {
pub struct CommonsPhaseConfig {
// The Hatch phase where initial contributors (Hatchers) participate in a hatch sale.
pub hatch: HatchConfig<T>,
pub hatch: HatchConfig,
// The Vesting phase where tokens minted during the Hatch phase are locked (burning is disabled) to combat early speculation/arbitrage.
// pub vesting: VestingConfig,
// The Open phase where anyone can mint tokens by contributing the reserve token into the curve and becoming members of the Commons.
Expand Down Expand Up @@ -174,24 +157,55 @@ pub enum CommonsPhase {
Hatch,
Open,
// TODO: should we allow for a closed phase?
Closed
Closed,
}

impl CommonsPhase {
pub fn expect_hatch(&self) -> Result<(), ContractError> {
ensure!(
matches!(self, CommonsPhase::Hatch),
ContractError::InvalidPhase {
expected: "Hatch".to_string(),
actual: format!("{:?}", self)
}
);
Ok(())
}

pub fn expect_open(&self) -> Result<(), ContractError> {
ensure!(
matches!(self, CommonsPhase::Open),
ContractError::InvalidPhase {
expected: "Open".to_string(),
actual: format!("{:?}", self)
}
);
Ok(())
}

pub fn expect_closed(&self) -> Result<(), ContractError> {
ensure!(
matches!(self, CommonsPhase::Closed),
ContractError::InvalidPhase {
expected: "Closed".to_string(),
actual: format!("{:?}", self)
}
);
Ok(())
}
}

impl CommonsPhaseConfig<String> {
impl CommonsPhaseConfig {
/// Validate that the commons configuration is valid
pub fn validate(&self, api: &dyn Api) -> Result<CommonsPhaseConfig<Addr>, ContractError> {
let hatch = self.hatch.validate(api)?;
pub fn validate(&self) -> Result<(), ContractError> {
self.hatch.validate()?;
self.open.validate()?;
self.closed.validate()?;

Ok(CommonsPhaseConfig {
hatch,
open: self.open.clone(),
closed: self.closed.clone(),
})
Ok(())
}
}


pub type CurveFn = Box<dyn Fn(DecimalPlaces) -> Box<dyn Curve>>;

#[cw_serde]
Expand Down Expand Up @@ -228,4 +242,3 @@ impl CurveType {
}
}
}

43 changes: 43 additions & 0 deletions contracts/external/cw-abc/src/boot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::msg::*;
use boot_core::{contract, Contract, CwEnv};
#[cfg(feature = "daemon")]
use boot_core::{ArtifactsDir, Daemon, WasmPath};
use boot_core::{ContractWrapper, Mock, MockState, TxHandler, Uploadable};
use cosmwasm_std::Empty;
use token_bindings::{TokenFactoryMsg, TokenFactoryQuery};

#[contract(InstantiateMsg, ExecuteMsg, QueryMsg, Empty)]
pub struct CwAbc<Chain>;

impl<Chain: CwEnv> CwAbc<Chain> {
pub fn new(name: &str, chain: Chain) -> Self {
let contract = Contract::new(name, chain);
Self(contract)
}
}

/// Basic app for the token factory contract
/// TODO: should be in the bindings, along with custom handler for multi-test
pub(crate) type TokenFactoryBasicApp = boot_core::BasicApp<TokenFactoryMsg, TokenFactoryQuery>;

type TokenFactoryMock = Mock<MockState, TokenFactoryMsg, TokenFactoryQuery>;

impl Uploadable<TokenFactoryMock> for CwAbc<TokenFactoryMock> {
fn source(&self) -> <TokenFactoryMock as TxHandler>::ContractSource {
Box::new(ContractWrapper::new(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
))
}
}

#[cfg(feature = "daemon")]
impl Uploadable<Daemon> for CwAbc<Daemon> {
fn source(&self) -> <Daemon as TxHandler>::ContractSource {
ArtifactsDir::env()
.expect("Expected ARTIFACTS_DIR in env")
.find_wasm_path("cw_abc")
.unwrap()
}
}
Loading