diff --git a/Cargo.lock b/Cargo.lock index ef6b222c1..814550dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -734,6 +734,7 @@ dependencies = [ "cw-ownable", "cw-paginate-storage 2.4.1", "cw-storage-plus 1.2.0", + "cw-tokenfactory-issuer", "cw-utils 1.0.3", "cw2 1.1.2", "dao-testing", diff --git a/contracts/external/cw-abc/Cargo.toml b/contracts/external/cw-abc/Cargo.toml index 643578f83..0e0ec211f 100644 --- a/contracts/external/cw-abc/Cargo.toml +++ b/contracts/external/cw-abc/Cargo.toml @@ -38,6 +38,7 @@ getrandom = { version = "0.2", features = ["js"] } token-bindings = { workspace = true } cw-ownable = { workspace = true } cw-paginate-storage = { workspace = true } +cw-tokenfactory-issuer = { workspace = true, features = ["library"] } # cw-orch = { version = "0.13.3", optional = true } [dev-dependencies] diff --git a/contracts/external/cw-abc/src/abc.rs b/contracts/external/cw-abc/src/abc.rs index eec4a40bb..eef4c7db3 100644 --- a/contracts/external/cw-abc/src/abc.rs +++ b/contracts/external/cw-abc/src/abc.rs @@ -10,7 +10,7 @@ pub struct SupplyToken { /// The denom to create for the supply token pub subdenom: String, /// Metadata for the supply token to create - pub metadata: Metadata, + 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, diff --git a/contracts/external/cw-abc/src/contract.rs b/contracts/external/cw-abc/src/contract.rs index 9d13ab0e5..8a2023d3d 100644 --- a/contracts/external/cw-abc/src/contract.rs +++ b/contracts/external/cw-abc/src/contract.rs @@ -1,25 +1,33 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, + Uint128, WasmMsg, +}; use cw2::set_contract_version; +use cw_tokenfactory_issuer::msg::{ + ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, +}; +use cw_utils::{nonpayable, parse_reply_instantiate_data}; use std::collections::HashSet; - use token_bindings::{TokenFactoryMsg, TokenFactoryQuery}; use crate::abc::{CommonsPhase, CurveFn}; use crate::curves::DecimalPlaces; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, UpdatePhaseConfigMsg}; +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, }; use crate::{commands, queries}; -use cw_utils::nonpayable; // version info for migration info pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-abc"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +const INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID: u64 = 0; + // By default, the prefix for token factory tokens is "factory" const DENOM_PREFIX: &str = "factory"; @@ -41,6 +49,7 @@ pub fn instantiate( curve_type, phase_config, hatcher_allowlist, + token_issuer_code_id, } = msg; if supply.subdenom.is_empty() { @@ -51,27 +60,23 @@ pub fn instantiate( phase_config.validate()?; - // TODO utilize cw-tokenfactory-issuer? - // Create supply denom with metadata - let create_supply_denom_msg = TokenFactoryMsg::CreateDenom { - subdenom: supply.subdenom.clone(), - metadata: Some(supply.metadata), - }; - // Tnstantiate cw-token-factory-issuer contract // DAO (sender) is set as contract admin - // let issuer_instantiate_msg = SubMsg::reply_always( - // WasmMsg::Instantiate { - // admin: Some(info.sender.to_string()), - // code_id: msg.token_issuer_code_id, - // msg: to_binary(&IssuerInstantiateMsg::NewToken { - // subdenom: supply.subdenom.clone(), - // })?, - // funds: info.funds, - // label: "cw-tokenfactory-issuer".to_string(), - // }, - // INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, - // ); + let issuer_instantiate_msg = SubMsg::reply_always( + WasmMsg::Instantiate { + admin: Some(info.sender.to_string()), + code_id: token_issuer_code_id, + msg: to_binary(&IssuerInstantiateMsg::NewToken { + subdenom: supply.subdenom.clone(), + })?, + funds: info.funds, + label: "cw-tokenfactory-issuer".to_string(), + }, + INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, + ); + + // Save new token info for use in reply + TOKEN_INSTANTIATION_INFO.save(deps.storage, &supply)?; // Save the denom SUPPLY_DENOM.save( @@ -105,7 +110,7 @@ pub fn instantiate( cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; - Ok(Response::default().add_message(create_supply_denom_msg)) + Ok(Response::default().add_submessage(issuer_instantiate_msg)) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -195,48 +200,126 @@ pub fn do_query( } } -// fn validate_denom( -// deps: DepsMut, -// denom: String, -// ) -> Result<(), TokenFactoryError> { -// let denom_to_split = denom.clone(); -// let tokenfactory_denom_parts: Vec<&str> = denom_to_split.split('/').collect(); - -// if tokenfactory_denom_parts.len() != 3 { -// return Result::Err(TokenFactoryError::InvalidDenom { -// denom, -// message: std::format!( -// "denom must have 3 parts separated by /, had {}", -// tokenfactory_denom_parts.len() -// ), -// }); -// } - -// let prefix = tokenfactory_denom_parts[0]; -// let creator_address = tokenfactory_denom_parts[1]; -// let subdenom = tokenfactory_denom_parts[2]; - -// if !prefix.eq_ignore_ascii_case("factory") { -// return Result::Err(TokenFactoryError::InvalidDenom { -// denom, -// message: std::format!("prefix must be 'factory', was {}", prefix), -// }); -// } - -// // Validate denom by attempting to query for full denom -// let response = TokenQuerier::new(&deps.querier) -// .full_denom(String::from(creator_address), String::from(subdenom)); -// if response.is_err() { -// return Result::Err(TokenFactoryError::InvalidDenom { -// denom, -// message: response.err().unwrap().to_string(), -// }); -// } - -// Result::Ok(()) -// } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + deps: DepsMut, + _env: Env, + _msg: MigrateMsg, +) -> Result, ContractError> { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply( + deps: DepsMut, + env: Env, + msg: Reply, +) -> Result, ContractError> { + match msg.id { + INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID => { + // Parse and save address of cw-tokenfactory-issuer + let issuer_addr = parse_reply_instantiate_data(msg)?.contract_address; + TOKEN_ISSUER_CONTRACT.save(deps.storage, &deps.api.addr_validate(&issuer_addr)?)?; + + // Load info for new token and remove temporary data + let token_info = TOKEN_INSTANTIATION_INFO.load(deps.storage)?; + TOKEN_INSTANTIATION_INFO.remove(deps.storage); + + // // Load the DAO address + // let dao = DAO.load(deps.storage)?; + + // Format the denom and save it + let denom = format!("factory/{}/{}", &issuer_addr, token_info.subdenom); + + SUPPLY_DENOM.save(deps.storage, &denom)?; + + // // Check supply is greater than zero, iterate through initial + // // balances and sum them, add DAO balance as well. + // let initial_supply = token + // .initial_balances + // .iter() + // .fold(Uint128::zero(), |previous, new_balance| { + // previous + new_balance.amount + // }); + // let total_supply = initial_supply + token.initial_dao_balance.unwrap_or_default(); + + // // Cannot instantiate with no initial token owners because it would + // // immediately lock the DAO. + // if initial_supply.is_zero() { + // return Err(ContractError::InitialBalancesError {}); + // } + + // Msgs to be executed to finalize setup + let mut msgs: Vec = vec![]; + + // Grant an allowance to mint + msgs.push(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_binary(&IssuerExecuteMsg::SetMinterAllowance { + 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![], + // }); + // } + + // TODO who should own the token contract? + // // Update issuer contract owner to be the DAO + // msgs.push(WasmMsg::Execute { + // contract_addr: issuer_addr.clone(), + // msg: to_binary(&IssuerExecuteMsg::ChangeContractOwner { + // new_owner: dao.to_string(), + // })?, + // funds: vec![], + // }); + + Ok(Response::new() + .add_attribute("cw-tokenfactory-issuer-address", issuer_addr) + .add_attribute("denom", denom) + .add_messages(msgs)) + } + _ => Err(ContractError::UnknownReplyId { id: msg.id }), + } +} -// // this is poor man's "skip" flag // #[cfg(test)] // pub(crate) mod tests { // use super::*; diff --git a/contracts/external/cw-abc/src/error.rs b/contracts/external/cw-abc/src/error.rs index 2e3989515..eae842541 100644 --- a/contracts/external/cw-abc/src/error.rs +++ b/contracts/external/cw-abc/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::StdError; -use cw_utils::PaymentError; +use cw_utils::{ParseReplyError, PaymentError}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -7,9 +7,12 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Payment(#[from] PaymentError), + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), + #[error("Invalid subdenom: {subdenom:?}")] InvalidSubdenom { subdenom: String }, @@ -42,4 +45,7 @@ pub enum ContractError { #[error("Invalid phase, expected {expected:?}, actual {actual:?}")] InvalidPhase { expected: String, actual: String }, + + #[error("Got a submessage reply with unknown id: {id}")] + UnknownReplyId { id: u64 }, } diff --git a/contracts/external/cw-abc/src/msg.rs b/contracts/external/cw-abc/src/msg.rs index 89b43239c..69d79d81c 100644 --- a/contracts/external/cw-abc/src/msg.rs +++ b/contracts/external/cw-abc/src/msg.rs @@ -5,19 +5,22 @@ use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType, MinMax, ReserveTok #[cw_serde] pub struct InstantiateMsg { - // Supply token information + /// The code id of the cw-tokenfactory-issuer contract + pub token_issuer_code_id: u64, + + /// Supply token information pub supply: SupplyToken, - // Reserve token information + /// Reserve token information pub reserve: ReserveToken, - // Curve type for this contract + /// Curve type for this contract pub curve_type: CurveType, - // Hatch configuration information + /// Hatch configuration information pub phase_config: CommonsPhaseConfig, - // Hatcher allowlist + /// Hatcher allowlist pub hatcher_allowlist: Option>, } @@ -59,6 +62,7 @@ pub enum ExecuteMsg { UpdatePhaseConfig(UpdatePhaseConfigMsg), } +// TODO token contract query #[cw_ownable::cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] @@ -89,44 +93,44 @@ pub enum QueryMsg { #[cw_serde] pub struct CurveInfoResponse { - // how many reserve tokens have been received + /// How many reserve tokens have been received pub reserve: Uint128, - // how many supply tokens have been issued + /// How many supply tokens have been issued pub supply: Uint128, - // the amount of tokens in the funding pool + /// The amount of tokens in the funding pool pub funding: Uint128, - // current spot price of the token + /// Current spot price of the token pub spot_price: Decimal, - // current reserve denom + /// Current reserve denom pub reserve_denom: String, } #[cw_serde] pub struct HatcherAllowlistResponse { - // hatcher allowlist + /// Hatcher allowlist pub allowlist: Option>, } #[cw_serde] pub struct CommonsPhaseConfigResponse { - // the phase configuration + /// The phase configuration pub phase_config: CommonsPhaseConfig, - // current phase + /// Current phase pub phase: CommonsPhase, } #[cw_serde] pub struct DonationsResponse { - // the donators mapped to their donation in the reserve token + /// The donators mapped to their donation in the reserve token pub donations: Vec<(Addr, Uint128)>, } #[cw_serde] pub struct HatchersResponse { - // the hatchers mapped to their contribution in the reserve token + /// The hatchers mapped to their contribution in the reserve token pub hatchers: Vec<(Addr, Uint128)>, } #[cw_serde] -pub enum MigrateMsg {} +pub struct MigrateMsg {} diff --git a/contracts/external/cw-abc/src/state.rs b/contracts/external/cw-abc/src/state.rs index cb885cc53..2af899c43 100644 --- a/contracts/external/cw-abc/src/state.rs +++ b/contracts/external/cw-abc/src/state.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use std::collections::HashSet; -use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType}; +use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType, SupplyToken}; use cosmwasm_std::{Addr, Uint128}; use cw_storage_plus::{Item, Map}; @@ -60,3 +60,9 @@ pub static PHASE_CONFIG: Item = Item::new("phase_config"); /// The phase state of the Augmented Bonding Curve pub static PHASE: Item = Item::new("phase"); + +/// Temporarily holds token_instantiation_info when creating a new Token Factory denom +pub const TOKEN_INSTANTIATION_INFO: Item = Item::new("token_instantiation_info"); + +/// The address of the cw-tokenfactory-issuer contract +pub const TOKEN_ISSUER_CONTRACT: Item = Item::new("token_issuer_contract");