From 5fc2de805d1c15ced8f0a62d63805e8bf42f7849 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Thu, 16 Nov 2023 15:04:37 +0100 Subject: [PATCH] Refactor update phase config, fix metadata, clean up --- Cargo.lock | 1 + contracts/external/cw-abc/Cargo.toml | 9 +- contracts/external/cw-abc/src/abc.rs | 7 +- contracts/external/cw-abc/src/commands.rs | 93 +++++++++++------ contracts/external/cw-abc/src/contract.rs | 115 +++++++++------------- contracts/external/cw-abc/src/msg.rs | 3 +- contracts/external/cw-abc/src/testing.rs | 18 ++-- 7 files changed, 128 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43301950f..0fd7a8ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -737,6 +737,7 @@ dependencies = [ "cw-tokenfactory-issuer", "cw-utils 1.0.3", "cw2 1.1.2", + "dao-interface", "dao-testing", "getrandom", "integer-cbrt", diff --git a/contracts/external/cw-abc/Cargo.toml b/contracts/external/cw-abc/Cargo.toml index cd2616c95..1676c2fa5 100644 --- a/contracts/external/cw-abc/Cargo.toml +++ b/contracts/external/cw-abc/Cargo.toml @@ -31,16 +31,17 @@ cw2 = { workspace = true } cw-storage-plus = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } -thiserror = { workspace = true } cw-address-like = { workspace = true } +cw-ownable = { workspace = true } +cw-paginate-storage = { workspace = true } +cw-tokenfactory-issuer = { workspace = true, features = ["library"] } +dao-interface = { workspace = true } rust_decimal = { workspace = true } integer-sqrt = { workspace = true } integer-cbrt = { workspace = true } getrandom = { workspace = true, features = ["js"] } +thiserror = { workspace = true } token-bindings = { workspace = true } -cw-ownable = { workspace = true } -cw-paginate-storage = { workspace = true } -cw-tokenfactory-issuer = { workspace = true, features = ["library"] } [dev-dependencies] speculoos = { workspace = true } diff --git a/contracts/external/cw-abc/src/abc.rs b/contracts/external/cw-abc/src/abc.rs index 0adb044ce..3014fbd74 100644 --- a/contracts/external/cw-abc/src/abc.rs +++ b/contracts/external/cw-abc/src/abc.rs @@ -1,16 +1,16 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Decimal as StdDecimal, Uint128}; +use dao_interface::token::NewDenomMetadata; use crate::curves::{decimal, Constant, Curve, DecimalPlaces, Linear, SquareRoot}; use crate::ContractError; -use token_bindings::Metadata; #[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: Option, + pub metadata: Option, /// Number of decimal places for the supply token, needed for proper curve math. /// Default for token factory is 6 pub decimals: u8, @@ -87,8 +87,8 @@ impl HatchConfig { #[cw_serde] pub struct OpenConfig { - // TODO isn't this the same as initial_allocation_ratio? Maybe clearer to just call it an entrance fee? /// Percentage of capital put into the Reserve Pool during the Open phase + /// when buying from the curve. pub allocation_percentage: StdDecimal, /// Exit taxation ratio pub exit_tax: StdDecimal, @@ -129,6 +129,7 @@ impl ClosedConfig { pub struct CommonsPhaseConfig { /// The Hatch phase where initial contributors (Hatchers) participate in a hatch sale. pub hatch: HatchConfig, + /// TODO Vest tokens after hatch phase /// 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. diff --git a/contracts/external/cw-abc/src/commands.rs b/contracts/external/cw-abc/src/commands.rs index e807091d8..0c3012a8b 100644 --- a/contracts/external/cw-abc/src/commands.rs +++ b/contracts/external/cw-abc/src/commands.rs @@ -10,18 +10,17 @@ use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; use crate::abc::{CommonsPhase, CurveFn, MinMax}; use crate::contract::CwAbcResult; +use crate::msg::UpdatePhaseConfigMsg; use crate::state::{ - CURVE_STATE, DONATIONS, HATCHERS, HATCHER_ALLOWLIST, PHASE, PHASE_CONFIG, SUPPLY_DENOM, - TOKEN_ISSUER_CONTRACT, + CURVE_STATE, CURVE_TYPE, DONATIONS, HATCHERS, HATCHER_ALLOWLIST, PHASE, PHASE_CONFIG, + SUPPLY_DENOM, TOKEN_ISSUER_CONTRACT, }; use crate::ContractError; -pub fn execute_buy( - deps: DepsMut, - _env: Env, - info: MessageInfo, - curve_fn: CurveFn, -) -> CwAbcResult { +pub fn execute_buy(deps: DepsMut, _env: Env, info: MessageInfo) -> CwAbcResult { + let curve_type = CURVE_TYPE.load(deps.storage)?; + let curve_fn = curve_type.to_curve_fn(); + let mut curve_state = CURVE_STATE.load(deps.storage)?; let payment = must_pay(&info, &curve_state.reserve_denom)?; @@ -115,12 +114,10 @@ fn update_hatcher_contributions( Ok(()) } -pub fn execute_sell( - deps: DepsMut, - _env: Env, - info: MessageInfo, - curve_fn: CurveFn, -) -> CwAbcResult { +pub fn execute_sell(deps: DepsMut, _env: Env, info: MessageInfo) -> CwAbcResult { + let curve_type = CURVE_TYPE.load(deps.storage)?; + let curve_fn = curve_type.to_curve_fn(); + let supply_denom = SUPPLY_DENOM.load(deps.storage)?; let burn_amount = must_pay(&info, &supply_denom)?; @@ -281,35 +278,71 @@ pub fn update_hatch_allowlist( Ok(Response::new().add_attributes(vec![("action", "update_hatch_allowlist")])) } -/// Update the hatch config -pub fn update_hatch_config( +pub fn update_phase_config( deps: DepsMut, _env: Env, info: MessageInfo, - initial_raise: Option, - initial_allocation_ratio: Option, + update_phase_config_msg: UpdatePhaseConfigMsg, ) -> CwAbcResult { // Assert that the sender is the contract owner cw_ownable::assert_owner(deps.storage, &info.sender)?; - // Ensure we're in the Hatch phase - PHASE.load(deps.storage)?.expect_hatch()?; + // Load phase and phase config + let phase = PHASE.load(deps.storage)?; // Load the current phase config let mut phase_config = PHASE_CONFIG.load(deps.storage)?; - // Update the hatch config if new values are provided - if let Some(initial_raise) = initial_raise { - phase_config.hatch.initial_raise = initial_raise; - } - if let Some(initial_allocation_ratio) = initial_allocation_ratio { - phase_config.hatch.initial_allocation_ratio = initial_allocation_ratio; - } + match update_phase_config_msg { + UpdatePhaseConfigMsg::Hatch { + exit_tax, + initial_raise, + initial_allocation_ratio, + } => { + // Check we are in the hatch phase + phase.expect_hatch()?; + + // Update the hatch config if new values are provided + if let Some(initial_raise) = initial_raise { + phase_config.hatch.initial_raise = initial_raise; + } + if let Some(initial_allocation_ratio) = initial_allocation_ratio { + phase_config.hatch.initial_allocation_ratio = initial_allocation_ratio; + } + if let Some(exit_tax) = exit_tax { + phase_config.hatch.exit_tax = exit_tax; + } - phase_config.hatch.validate()?; - PHASE_CONFIG.save(deps.storage, &phase_config)?; + // Validate config + phase_config.hatch.validate()?; + PHASE_CONFIG.save(deps.storage, &phase_config)?; - Ok(Response::new().add_attribute("action", "update_hatch_config")) + Ok(Response::new().add_attribute("action", "update_hatch_phase_config")) + } + UpdatePhaseConfigMsg::Open { + exit_tax, + allocation_percentage, + } => { + // Check we are in the open phase + phase.expect_open()?; + + // Update the hatch config if new values are provided + if let Some(allocation_percentage) = allocation_percentage { + phase_config.open.allocation_percentage = allocation_percentage; + } + if let Some(exit_tax) = exit_tax { + phase_config.hatch.exit_tax = exit_tax; + } + + // Validate config + phase_config.open.validate()?; + PHASE_CONFIG.save(deps.storage, &phase_config)?; + + Ok(Response::new().add_attribute("action", "update_open_phase_config")) + } + // TODO what should the closed phase configuration be, is there one? + _ => todo!(), + } } /// Update the ownership of the contract diff --git a/contracts/external/cw-abc/src/contract.rs b/contracts/external/cw-abc/src/contract.rs index 8afb920e3..ba3dd78ef 100644 --- a/contracts/external/cw-abc/src/contract.rs +++ b/contracts/external/cw-abc/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; use cw_tokenfactory_issuer::msg::{ - ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, + DenomUnit, ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, Metadata, }; use cw_utils::parse_reply_instantiate_data; use std::collections::HashSet; @@ -120,44 +120,17 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> CwAbcResult { - // default implementation stores curve info as enum, you can do something else in a derived - // contract and just pass in your custom curve to do_execute - let curve_type = CURVE_TYPE.load(deps.storage)?; - let curve_fn = curve_type.to_curve_fn(); - do_execute(deps, env, info, msg, curve_fn) -} - -/// We pull out logic here, so we can import this from another contract and set a different Curve. -/// This contacts sets a curve with an enum in InstantiateMsg and stored in state, but you may want -/// to use custom math not included - make this easily reusable -pub fn do_execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, - curve_fn: CurveFn, ) -> CwAbcResult { match msg { - ExecuteMsg::Buy {} => commands::execute_buy(deps, env, info, curve_fn), - ExecuteMsg::Burn {} => commands::execute_sell(deps, env, info, curve_fn), + ExecuteMsg::Buy {} => commands::execute_buy(deps, env, info), + ExecuteMsg::Burn {} => commands::execute_sell(deps, env, info), ExecuteMsg::Donate {} => commands::execute_donate(deps, env, info), ExecuteMsg::UpdateHatchAllowlist { to_add, to_remove } => { commands::update_hatch_allowlist(deps, info, to_add, to_remove) } - ExecuteMsg::UpdatePhaseConfig(update) => match update { - UpdatePhaseConfigMsg::Hatch { - initial_raise, - initial_allocation_ratio, - } => commands::update_hatch_config( - deps, - env, - info, - initial_raise, - initial_allocation_ratio, - ), - _ => todo!(), - }, + ExecuteMsg::UpdatePhaseConfig(update_msg) => { + commands::update_phase_config(deps, env, info, update_msg) + } ExecuteMsg::UpdateOwnership(action) => { commands::update_ownership(deps, &env, &info, action) } @@ -231,13 +204,15 @@ pub fn reply( SUPPLY_DENOM.save(deps.storage, &denom)?; // Msgs to be executed to finalize setup - let msgs: Vec = vec![ + let mut msgs: Vec = vec![ // Grant an allowance to mint WasmMsg::Execute { contract_addr: issuer_addr.clone(), msg: to_binary(&IssuerExecuteMsg::SetMinterAllowance { address: env.contract.address.to_string(), - // TODO let this be capped + // Allowance needs to be max as this the is the amount of tokens + // the minter is allowed to mint, not to be confused with max supply + // which we have to enforce elsewhere. allowance: Uint128::MAX, })?, funds: vec![], @@ -247,48 +222,46 @@ pub fn reply( contract_addr: issuer_addr.clone(), msg: to_binary(&IssuerExecuteMsg::SetBurnerAllowance { address: env.contract.address.to_string(), - // TODO let this be capped allowance: Uint128::MAX, })?, funds: vec![], }, ]; - // TODO fix metadata - // // If metadata, set it by calling the contract - // if let Some(metadata) = token_info.metadata { - // // The first denom_unit must be the same as the tf and base denom. - // // It must have an exponent of 0. This the smallest unit of the token. - // // For more info: // https://docs.cosmos.network/main/architecture/adr-024-coin-metadata - // let mut denom_units = vec![DenomUnit { - // denom: denom.clone(), - // exponent: 0, - // aliases: vec![token_info.subdenom], - // }]; - - // // Caller can optionally define additional units - // if let Some(mut additional_units) = metadata.additional_denom_units { - // denom_units.append(&mut additional_units); - // } - - // // Sort denom units by exponent, must be in ascending order - // denom_units.sort_by(|a, b| a.exponent.cmp(&b.exponent)); - - // msgs.push(WasmMsg::Execute { - // contract_addr: issuer_addr.clone(), - // msg: to_binary(&IssuerExecuteMsg::SetDenomMetadata { - // metadata: Metadata { - // description: metadata.description, - // denom_units, - // base: denom.clone(), - // display: metadata.display, - // name: metadata.name, - // symbol: metadata.symbol, - // }, - // })?, - // funds: vec![], - // }); - // } + // If metadata, set it by calling the contract + if let Some(metadata) = token_info.metadata { + // The first denom_unit must be the same as the tf and base denom. + // It must have an exponent of 0. This the smallest unit of the token. + // For more info: // https://docs.cosmos.network/main/architecture/adr-024-coin-metadata + let mut denom_units = vec![DenomUnit { + denom: denom.clone(), + exponent: 0, + aliases: vec![token_info.subdenom], + }]; + + // Caller can optionally define additional units + if let Some(mut additional_units) = metadata.additional_denom_units { + denom_units.append(&mut additional_units); + } + + // Sort denom units by exponent, must be in ascending order + denom_units.sort_by(|a, b| a.exponent.cmp(&b.exponent)); + + msgs.push(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_binary(&IssuerExecuteMsg::SetDenomMetadata { + metadata: Metadata { + description: metadata.description, + denom_units, + base: denom.clone(), + display: metadata.display, + name: metadata.name, + symbol: metadata.symbol, + }, + })?, + funds: vec![], + }); + } Ok(Response::new() .add_attribute("cw-tokenfactory-issuer-address", issuer_addr) diff --git a/contracts/external/cw-abc/src/msg.rs b/contracts/external/cw-abc/src/msg.rs index 848268288..b1b04ce52 100644 --- a/contracts/external/cw-abc/src/msg.rs +++ b/contracts/external/cw-abc/src/msg.rs @@ -30,13 +30,14 @@ pub struct InstantiateMsg { pub enum UpdatePhaseConfigMsg { /// Update the hatch phase configuration Hatch { + exit_tax: Option, initial_raise: Option, initial_allocation_ratio: Option, }, /// Update the open phase configuration Open { exit_tax: Option, - reserve_ratio: Option, + allocation_percentage: Option, }, /// Update the closed phase configuration Closed {}, diff --git a/contracts/external/cw-abc/src/testing.rs b/contracts/external/cw-abc/src/testing.rs index 38999f0b5..f3ebcc943 100644 --- a/contracts/external/cw-abc/src/testing.rs +++ b/contracts/external/cw-abc/src/testing.rs @@ -2,8 +2,9 @@ use cosmwasm_std::{ testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, Decimal, DepsMut, OwnedDeps, Uint128, }; +use dao_interface::token::NewDenomMetadata; use std::marker::PhantomData; -use token_bindings::{Metadata, TokenFactoryQuery}; +use token_bindings::TokenFactoryQuery; use crate::abc::{ ClosedConfig, CommonsPhaseConfig, CurveType, HatchConfig, MinMax, OpenConfig, ReserveToken, @@ -29,14 +30,13 @@ pub const _TEST_BUYER: &str = "buyer"; pub const TEST_SUPPLY_DENOM: &str = "subdenom"; -pub fn default_supply_metadata() -> Metadata { - Metadata { - name: Some("Bonded".to_string()), - symbol: Some("EPOXY".to_string()), - description: None, - denom_units: vec![], - base: None, - display: None, +pub fn default_supply_metadata() -> NewDenomMetadata { + NewDenomMetadata { + name: "Bonded".to_string(), + symbol: "EPOXY".to_string(), + description: "Forever".to_string(), + display: "EPOXY".to_string(), + additional_denom_units: None, } }