Skip to content

Commit

Permalink
cw-abc: Updated hatch phase mechanics, donations, queries (#699)
Browse files Browse the repository at this point in the history
* Separate hatcher allowlist

* Donation feature

* Initial sell exit tax

* Hatchers to amount

* Hatch phase exit tax

* TokenMsg methods

* Format

* Hatchers query

* Fix bug where float was not taken into account in supply

* Buy and sell refactoring

* Update hatch phase config

* Update phase config enum

* Add adairrr to authors

* Initial boot integration with custom msgs

* Initial testing infrastructure

* Abstract-OS to AbstractSDK
  • Loading branch information
adairrr authored and Jake Hartnell committed Mar 26, 2024
1 parent aceb8ba commit 52c6a98
Show file tree
Hide file tree
Showing 11 changed files with 973 additions and 416 deletions.
19 changes: 18 additions & 1 deletion 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"]

[dependencies]
cw-utils = { workspace = true }
Expand All @@ -32,7 +33,23 @@ integer-cbrt = "0.1.2"
# TODO publish this
token-bindings = { git = "https://github.com/CosmosContracts/token-bindings", rev = "1412b94" }
cw-ownable = { workspace = true }
cw-paginate-storage = { workspace = true }
boot-core = { version = "0.10.0", optional = true, git = "https://github.com/AbstractSDK/BOOT", branch = "fix/custom_binding_contract_wrapper" }

[dev-dependencies]
# TODO move to workspace
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

0 comments on commit 52c6a98

Please sign in to comment.