diff --git a/contracts/external/cw-abc/src/abc.rs b/contracts/external/cw-abc/src/abc.rs index 67febb7df..aa5a3c713 100644 --- a/contracts/external/cw-abc/src/abc.rs +++ b/contracts/external/cw-abc/src/abc.rs @@ -14,8 +14,8 @@ pub struct SupplyToken { /// Number of decimal places for the supply token, needed for proper curve math. /// Default for token factory is 6 pub decimals: u8, - // TODO max supply - // pub max_supply: Uint128, + // Optional maximum supply + pub max_supply: Option, } #[cw_serde] diff --git a/contracts/external/cw-abc/src/commands.rs b/contracts/external/cw-abc/src/commands.rs index 3083378dc..4ed60984f 100644 --- a/contracts/external/cw-abc/src/commands.rs +++ b/contracts/external/cw-abc/src/commands.rs @@ -8,12 +8,12 @@ use std::collections::HashSet; use std::ops::Deref; use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; -use crate::abc::{CommonsPhase, CurveFn, MinMax}; +use crate::abc::CommonsPhase; use crate::contract::CwAbcResult; use crate::msg::UpdatePhaseConfigMsg; use crate::state::{ - CURVE_STATE, CURVE_TYPE, DONATIONS, HATCHERS, HATCHER_ALLOWLIST, PHASE, PHASE_CONFIG, - SUPPLY_DENOM, TOKEN_ISSUER_CONTRACT, + CURVE_STATE, CURVE_TYPE, DONATIONS, HATCHERS, HATCHER_ALLOWLIST, MAX_SUPPLY, PHASE, + PHASE_CONFIG, SUPPLY_DENOM, TOKEN_ISSUER_CONTRACT, }; use crate::ContractError; @@ -81,6 +81,15 @@ pub fn execute_buy(deps: DepsMut, _env: Env, info: MessageInf let minted = new_supply .checked_sub(curve_state.supply) .map_err(StdError::overflow)?; + + // Check that the minted amount has not exceeded the max supply (if configured) + if let Some(max_supply) = MAX_SUPPLY.may_load(deps.storage)? { + if new_supply > max_supply { + return Err(ContractError::CannotExceedMaxSupply { max: max_supply }); + } + } + + // Save the new curve state curve_state.supply = new_supply; CURVE_STATE.save(deps.storage, &curve_state)?; @@ -95,8 +104,6 @@ pub fn execute_buy(deps: DepsMut, _env: Env, info: MessageInf funds: vec![], }; - // TODO check that the minted amount has not exceeded the max supply - Ok(Response::new() .add_message(mint_msg) .add_attribute("action", "buy") diff --git a/contracts/external/cw-abc/src/contract.rs b/contracts/external/cw-abc/src/contract.rs index ba3dd78ef..bf94e9707 100644 --- a/contracts/external/cw-abc/src/contract.rs +++ b/contracts/external/cw-abc/src/contract.rs @@ -17,8 +17,8 @@ use crate::curves::DecimalPlaces; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, UpdatePhaseConfigMsg}; use crate::state::{ - CurveState, CURVE_STATE, CURVE_TYPE, HATCHER_ALLOWLIST, PHASE, PHASE_CONFIG, SUPPLY_DENOM, - TOKEN_INSTANTIATION_INFO, TOKEN_ISSUER_CONTRACT, + CurveState, CURVE_STATE, CURVE_TYPE, HATCHER_ALLOWLIST, MAX_SUPPLY, PHASE, PHASE_CONFIG, + SUPPLY_DENOM, TOKEN_INSTANTIATION_INFO, TOKEN_ISSUER_CONTRACT, }; use crate::{commands, queries}; @@ -62,16 +62,9 @@ pub fn instantiate( // Save new token info for use in reply TOKEN_INSTANTIATION_INFO.save(deps.storage, &supply)?; - // Save the denom - SUPPLY_DENOM.save( - deps.storage, - &format!( - "{}/{}/{}", - DENOM_PREFIX, - env.contract.address.into_string(), - supply.subdenom - ), - )?; + if let Some(max_supply) = supply.max_supply { + MAX_SUPPLY.save(deps.storage, &max_supply)?; + } // Save the curve type and state let normalization_places = DecimalPlaces::new(supply.decimals, reserve.decimals); diff --git a/contracts/external/cw-abc/src/error.rs b/contracts/external/cw-abc/src/error.rs index 369f2767d..d62661331 100644 --- a/contracts/external/cw-abc/src/error.rs +++ b/contracts/external/cw-abc/src/error.rs @@ -16,14 +16,17 @@ pub enum ContractError { #[error("{0}")] Ownership(#[from] cw_ownable::OwnershipError), + #[error("Cannot mint more tokens than the maximum supply of {max}")] + CannotExceedMaxSupply { max: Uint128 }, + #[error("The commons is closed to new contributions")] CommonsClosed {}, #[error("Contribution must be less than or equal to {max} and greater than or equal to {min}")] ContributionLimit { min: Uint128, max: Uint128 }, - #[error("Selling is disabled during the hatch phase")] - HatchSellingDisabled {}, + #[error("Hatch phase config error {0}")] + HatchPhaseConfigError(String), #[error("Invalid subdenom: {subdenom:?}")] InvalidSubdenom { subdenom: String }, @@ -34,9 +37,6 @@ pub enum ContractError { #[error("Invalid sell amount")] MismatchedSellAmount {}, - #[error("Hatch phase config error {0}")] - HatchPhaseConfigError(String), - #[error("Open phase config error {0}")] OpenPhaseConfigError(String), diff --git a/contracts/external/cw-abc/src/state.rs b/contracts/external/cw-abc/src/state.rs index 2af899c43..f3b9b55a3 100644 --- a/contracts/external/cw-abc/src/state.rs +++ b/contracts/external/cw-abc/src/state.rs @@ -43,6 +43,9 @@ pub const CURVE_TYPE: Item = Item::new("curve_type"); /// The denom used for the supply token pub const SUPPLY_DENOM: Item = Item::new("denom"); +/// The maximum supply of the supply token, new tokens cannot be minted beyond this cap +pub const MAX_SUPPLY: Item = Item::new("max_supply"); + /// Hatcher phase allowlist /// TODO: we could use the keys for the [`HATCHERS`] map instead setting them to 0 at the beginning, though existing hatchers would not be able to be removed pub static HATCHER_ALLOWLIST: Item> = Item::new("hatch_allowlist"); diff --git a/contracts/external/cw-abc/src/test_tube/integration_tests.rs b/contracts/external/cw-abc/src/test_tube/integration_tests.rs index 49aad5b04..25482180a 100644 --- a/contracts/external/cw-abc/src/test_tube/integration_tests.rs +++ b/contracts/external/cw-abc/src/test_tube/integration_tests.rs @@ -1,8 +1,5 @@ use crate::{ - abc::{ - ClosedConfig, CommonsPhase, CommonsPhaseConfig, CurveType, HatchConfig, MinMax, OpenConfig, - ReserveToken, SupplyToken, - }, + abc::{ClosedConfig, CommonsPhase, CommonsPhaseConfig, HatchConfig, MinMax, OpenConfig}, msg::{ CommonsPhaseConfigResponse, CurveInfoResponse, DenomResponse, ExecuteMsg, InstantiateMsg, QueryMsg, @@ -216,8 +213,33 @@ fn test_contribution_limits_enforced() { ); } -// TODO #[test] -fn test_max_supply() { - // Set a max supply and ensure it does not go over +fn test_max_supply_enforced() { + let app = OsmosisTestApp::new(); + let builder = TestEnvBuilder::new(); + let env = builder.default_setup(&app); + let TestEnv { + ref abc, + ref accounts, + .. + } = env; + + // Buy enough tokens to end the hatch phase + abc.execute(&ExecuteMsg::Buy {}, &coins(1000000, RESERVE), &accounts[0]) + .unwrap(); + + // Buy enough tokens to trigger a max supply error + let err = abc + .execute( + &ExecuteMsg::Buy {}, + &coins(1000000000, RESERVE), + &accounts[0], + ) + .unwrap_err(); + assert_eq!( + err, + abc.execute_error(ContractError::CannotExceedMaxSupply { + max: Uint128::from(1000000u128) + }) + ); } diff --git a/contracts/external/cw-abc/src/test_tube/test_env.rs b/contracts/external/cw-abc/src/test_tube/test_env.rs index 55a20549f..069a8c2dc 100644 --- a/contracts/external/cw-abc/src/test_tube/test_env.rs +++ b/contracts/external/cw-abc/src/test_tube/test_env.rs @@ -110,6 +110,7 @@ impl TestEnvBuilder { subdenom: DENOM.to_string(), metadata: None, decimals: 6, + max_supply: Some(Uint128::from(1000000000u128)), }, reserve: ReserveToken { denom: RESERVE.to_string(), diff --git a/contracts/external/cw-abc/src/testing.rs b/contracts/external/cw-abc/src/testing.rs index c08a85914..247c99c27 100644 --- a/contracts/external/cw-abc/src/testing.rs +++ b/contracts/external/cw-abc/src/testing.rs @@ -51,6 +51,7 @@ pub fn default_instantiate_msg( subdenom: TEST_SUPPLY_DENOM.to_string(), metadata: Some(default_supply_metadata()), decimals, + max_supply: None, }, reserve: ReserveToken { denom: TEST_RESERVE_DENOM.to_string(),