Skip to content

Commit

Permalink
Initial phase integration to cw-abc (DA0-DA0#698)
Browse files Browse the repository at this point in the history
* CwAbcResult

* Hatch phase configuration and init msg refactor

* Initial instantiate test

* Implement separate phrases and phrase configs

* Remove vesting phase and update funding pool

* Separate commands and queries

* Update init msg with string configs

* Phase config query

* MinMax and config query

* Add some todos

* cw-ownable integration
  • Loading branch information
adairrr authored and Jake Hartnell committed Mar 26, 2024
1 parent 088143c commit aceb8ba
Show file tree
Hide file tree
Showing 10 changed files with 780 additions and 279 deletions.
79 changes: 79 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion contracts/external/cw-abc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ cw-storage-plus = { workspace = true }
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
thiserror = { workspace = true }
# TODO move to workspace
cw-address-like = "1.0.4"
rust_decimal = "1.14.3"
integer-sqrt = "0.1.5"
integer-cbrt = "0.1.2"
# TODO publish this
token-bindings = { git = "https://github.com/CosmosContracts/token-bindings", rev = "1412b94" }
cw-ownable = { workspace = true }

[dev-dependencies]

# TODO move to workspace
speculoos = "0.11.0"
231 changes: 231 additions & 0 deletions contracts/external/cw-abc/src/abc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@

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 crate::ContractError;

#[cw_serde]
pub struct SupplyToken {
// The denom to create for the supply token
pub subdenom: String,
// Metadata for the supply token to create
pub metadata: Metadata,
// Number of decimal places for the reserve token, needed for proper curve math.
// Same format as decimals above, eg. if it is uatom, where 1 unit is 10^-6 ATOM, use 6 here
pub decimals: u8,
}

#[cw_serde]
pub struct ReserveToken {
// Reserve token denom (only support native for now)
pub denom: String,
// Number of decimal places for the reserve token, needed for proper curve math.
// Same format as decimals above, eg. if it is uatom, where 1 unit is 10^-6 ATOM, use 6 here
pub decimals: u8,
}

/// Struct for minimium and maximum values
#[cw_serde]
pub struct MinMax {
pub min: Uint128,
pub max: Uint128,
}

#[cw_serde]
pub struct HatchConfig<T: AddressLike> {
// Initial contributors (Hatchers) allow list
pub allowlist: Option<Vec<T>>,
// /// 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
pub initial_raise: MinMax,
// The initial price (p0) per reserve token
// TODO: initial price is not implemented yet
pub initial_price: Uint128,
// The initial allocation (θ), percentage of the initial raise allocated to the Funding Pool
pub initial_allocation_ratio: 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> {
/// Validate the hatch config
pub fn validate(&self, api: &dyn Api) -> Result<HatchConfig<Addr>, ContractError> {
ensure!(
self.initial_raise.min < self.initial_raise.max,
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())
);

ensure!(
self.initial_allocation_ratio <= StdDecimal::percent(100u64),
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(),
}
);
}

Ok(())
}
}


#[cw_serde]
pub struct OpenConfig {
// Percentage of capital put into the Reserve Pool during the Open phase
pub allocation_percentage: 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())
);

Ok(())
}
}

#[cw_serde]
pub struct ClosedConfig {}


#[cw_serde]
pub struct CommonsPhaseConfig<T: AddressLike> {
// The Hatch phase where initial contributors (Hatchers) participate in a hatch sale.
pub hatch: HatchConfig<T>,
// 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.
pub open: OpenConfig,
// The Closed phase where the Commons is closed to new members.
pub closed: ClosedConfig,
}

// #[derive(Default)]
// #[cw_serde]
// pub struct HatchPhaseState {
// // Initial contributors (Hatchers)
// pub hatchers: HashSet<Addr>,
// }
//
// // TODO: maybe should be combined with config or just placed in state
// #[cw_serde]
// pub struct CommonsPhaseState {
// pub hatch: HatchPhaseState,
// // Vesting,
// pub open: (),
// // TODO: should we allow for a closed phase?
// pub closed: ()
// }

#[cw_serde]
pub enum CommonsPhase {
Hatch,
Open,
// TODO: should we allow for a closed phase?
Closed
}

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

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


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

#[cw_serde]
pub enum CurveType {
/// Constant always returns `value * 10^-scale` as spot price
Constant { value: Uint128, scale: u32 },
/// Linear returns `slope * 10^-scale * supply` as spot price
Linear { slope: Uint128, scale: u32 },
/// SquareRoot returns `slope * 10^-scale * supply^0.5` as spot price
SquareRoot { slope: Uint128, scale: u32 },
}

impl CurveType {
pub fn to_curve_fn(&self) -> CurveFn {
match self.clone() {
CurveType::Constant { value, scale } => {
let calc = move |places| -> Box<dyn Curve> {
Box::new(Constant::new(decimal(value, scale), places))
};
Box::new(calc)
}
CurveType::Linear { slope, scale } => {
let calc = move |places| -> Box<dyn Curve> {
Box::new(Linear::new(decimal(slope, scale), places))
};
Box::new(calc)
}
CurveType::SquareRoot { slope, scale } => {
let calc = move |places| -> Box<dyn Curve> {
Box::new(SquareRoot::new(decimal(slope, scale), places))
};
Box::new(calc)
}
}
}
}

Loading

0 comments on commit aceb8ba

Please sign in to comment.