From 11eaf889674b312d904c2c586705178fac0ddcef Mon Sep 17 00:00:00 2001 From: Gabriel Lopez Date: Wed, 3 Apr 2024 20:28:01 -0500 Subject: [PATCH] Allow existing tokens to be used with ABC's I think it's important for an existing token to be supported by an ABC. Token DAO's will be able to set up liquidity more easily by simply allowing mint & burn functionality after instantiating the ABC while holding ownership of it. *Also adds a query for hatcher allowlist *fixes spell checks I ran into *moves TokenInfo from dao-voting-token-staked to dao-interface for use in cw-abc --- contracts/external/cw-abc/Cargo.toml | 1 + contracts/external/cw-abc/README.md | 8 +- contracts/external/cw-abc/schema/cw-abc.json | 227 ++++++++++++++++-- contracts/external/cw-abc/src/abc.rs | 11 +- contracts/external/cw-abc/src/commands.rs | 16 +- contracts/external/cw-abc/src/contract.rs | 185 +++++++++++--- contracts/external/cw-abc/src/msg.rs | 16 +- contracts/external/cw-abc/src/queries.rs | 35 ++- contracts/external/cw-abc/src/state.rs | 7 +- .../cw-abc/src/test_tube/integration_tests.rs | 159 +++++++++++- .../external/cw-abc/src/test_tube/test_env.rs | 67 +++++- contracts/external/cw-abc/src/testing.rs | 12 +- .../cw-tokenfactory-issuer/src/msg.rs | 20 +- .../cw-tokenfactory-issuer/src/queries.rs | 4 +- .../dao-abc-factory/src/test_tube/test_env.rs | 25 +- .../src/testing/instantiate.rs | 2 +- .../src/testing/instantiate.rs | 2 +- .../dao-voting-token-staked/src/contract.rs | 8 +- .../voting/dao-voting-token-staked/src/msg.rs | 23 +- .../dao-voting-token-staked/src/state.rs | 3 +- .../src/tests/multitest/tests.rs | 3 +- .../src/tests/test_tube/integration_tests.rs | 4 +- .../src/tests/test_tube/test_env.rs | 4 +- packages/dao-interface/src/token.rs | 21 +- packages/dao-testing/src/test_tube/mod.rs | 2 +- 25 files changed, 706 insertions(+), 159 deletions(-) diff --git a/contracts/external/cw-abc/Cargo.toml b/contracts/external/cw-abc/Cargo.toml index 43abe028c..0b5f5f8f3 100644 --- a/contracts/external/cw-abc/Cargo.toml +++ b/contracts/external/cw-abc/Cargo.toml @@ -51,3 +51,4 @@ osmosis-std = { workspace = true } osmosis-test-tube = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +cw-tokenfactory-issuer = { workspace = true } diff --git a/contracts/external/cw-abc/README.md b/contracts/external/cw-abc/README.md index 44ab82194..a21a96c83 100644 --- a/contracts/external/cw-abc/README.md +++ b/contracts/external/cw-abc/README.md @@ -87,17 +87,17 @@ Example Instantiation message: "decimals": 6, "max_supply": "100000000000000" }, - reserve: { + "reserve": { "denom": "ujuno", "decimals": 6, }, - curve_type: { + "curve_type": { "linear": { "slope": "2", "scale": 1 } }, - phase_config: { + "phase_config": { "hatch": { "contribution_limits": { "min": "10000000", @@ -116,7 +116,7 @@ Example Instantiation message: }, "closed": {} }, - hatcher_allowlist: ["allowlist addresses, leave blank for no allowlist"], + "hatcher_allowlist": ["allowlist addresses, leave blank for no allowlist"], } ``` diff --git a/contracts/external/cw-abc/schema/cw-abc.json b/contracts/external/cw-abc/schema/cw-abc.json index 0a0187250..624b72d3a 100644 --- a/contracts/external/cw-abc/schema/cw-abc.json +++ b/contracts/external/cw-abc/schema/cw-abc.json @@ -11,8 +11,7 @@ "fees_recipient", "phase_config", "reserve", - "supply", - "token_issuer_code_id" + "supply" ], "properties": { "curve_type": { @@ -60,16 +59,14 @@ "$ref": "#/definitions/SupplyToken" } ] - }, - "token_issuer_code_id": { - "description": "The code id of the cw-tokenfactory-issuer contract", - "type": "integer", - "format": "uint64", - "minimum": 0.0 } }, "additionalProperties": false, "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, "ClosedConfig": { "type": "object", "additionalProperties": false @@ -273,8 +270,24 @@ }, "additionalProperties": false }, + "InitialBalance": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, "MinMax": { - "description": "Struct for minimium and maximum values", + "description": "Struct for minimum and maximum values", "type": "object", "required": [ "max", @@ -328,6 +341,56 @@ }, "additionalProperties": false }, + "NewTokenInfo": { + "type": "object", + "required": [ + "initial_balances", + "subdenom", + "token_issuer_code_id" + ], + "properties": { + "initial_balances": { + "description": "The initial balances to set for the token, cannot be empty.", + "type": "array", + "items": { + "$ref": "#/definitions/InitialBalance" + } + }, + "initial_dao_balance": { + "description": "Optional balance to mint for the DAO.", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "metadata": { + "description": "Optional metadata for the token, this can additionally be set later.", + "anyOf": [ + { + "$ref": "#/definitions/NewDenomMetadata" + }, + { + "type": "null" + } + ] + }, + "subdenom": { + "description": "The subdenom of the token to create, will also be used as an alias for the denom. The Token Factory denom will have the format of factory/{contract_address}/{subdenom}", + "type": "string" + }, + "token_issuer_code_id": { + "description": "The code id of the cw-tokenfactory-issuer contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, "OpenConfig": { "type": "object", "required": [ @@ -378,7 +441,7 @@ "type": "object", "required": [ "decimals", - "subdenom" + "token_info" ], "properties": { "decimals": { @@ -397,24 +460,70 @@ } ] }, - "metadata": { - "description": "Metadata for the supply token to create", - "anyOf": [ - { - "$ref": "#/definitions/NewDenomMetadata" - }, + "token_info": { + "description": "New or existing native token NOTE: If using an existing token, then the ABC must be given mint and burn permissions after creation", + "allOf": [ { - "type": "null" + "$ref": "#/definitions/TokenInfo" } ] - }, - "subdenom": { - "description": "The denom to create for the supply token", - "type": "string" } }, "additionalProperties": false }, + "TokenInfo": { + "oneOf": [ + { + "description": "Uses an existing Token Factory token and creates a new issuer contract. Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, must be done manually.", + "type": "object", + "required": [ + "existing" + ], + "properties": { + "existing": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "description": "Token factory denom", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Creates a new Token Factory token via the issue contract with the DAO automatically setup as admin and owner.", + "type": "object", + "required": [ + "new" + ], + "properties": { + "new": { + "$ref": "#/definitions/NewTokenInfo" + } + }, + "additionalProperties": false + }, + { + "description": "Uses a factory contract that must return the denom, optionally a Token Contract address. The binary must serialize to a `WasmMsg::Execute` message. Validation happens in the factory contract itself, so be sure to use a trusted factory contract.", + "type": "object", + "required": [ + "factory" + ], + "properties": { + "factory": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -783,7 +892,7 @@ ] }, "MinMax": { - "description": "Struct for minimium and maximum values", + "description": "Struct for minimum and maximum values", "type": "object", "required": [ "max", @@ -1005,7 +1114,7 @@ "additionalProperties": false }, { - "description": "Returns the Fee Recipient for the contract. This is the address that recieves any fees collected from bonding curve operation", + "description": "Returns the Fee Recipient for the contract. This is the address that receives any fees collected from bonding curve operation", "type": "object", "required": [ "fees_recipient" @@ -1049,7 +1158,37 @@ "additionalProperties": false }, { - "description": "Returns the Maxiumum Supply of the supply token", + "description": "Lists the hatcher allowlist Returns [`HatcherAllowlistResponse`]", + "type": "object", + "required": [ + "hatcher_allowlist" + ], + "properties": { + "hatcher_allowlist": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the Maximum Supply of the supply token", "type": "object", "required": [ "max_supply" @@ -1338,6 +1477,44 @@ "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, + "hatcher_allowlist": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HatcherAllowlistResponse", + "type": "object", + "properties": { + "allowlist": { + "description": "Hatcher allowlist", + "type": [ + "array", + "null" + ], + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/Addr" + }, + { + "$ref": "#/definitions/Empty" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } + }, "hatchers": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "HatchersResponse", @@ -1611,7 +1788,7 @@ "additionalProperties": false }, "MinMax": { - "description": "Struct for minimium and maximum values", + "description": "Struct for minimum and maximum values", "type": "object", "required": [ "max", diff --git a/contracts/external/cw-abc/src/abc.rs b/contracts/external/cw-abc/src/abc.rs index 30f1f430b..68f739156 100644 --- a/contracts/external/cw-abc/src/abc.rs +++ b/contracts/external/cw-abc/src/abc.rs @@ -1,16 +1,15 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Decimal as StdDecimal, Uint128}; -use dao_interface::token::NewDenomMetadata; +use dao_interface::token::TokenInfo; use crate::curves::{decimal, Constant, Curve, 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: Option, + /// New or existing native token + /// NOTE: If using an existing token, then the ABC must be given mint and burn permissions after creation + pub token_info: TokenInfo, /// Number of decimal places for the supply token, needed for proper curve math. /// Default for token factory is 6 pub decimals: u8, @@ -27,7 +26,7 @@ pub struct ReserveToken { pub decimals: u8, } -/// Struct for minimium and maximum values +/// Struct for minimum and maximum values #[cw_serde] pub struct MinMax { pub min: Uint128, diff --git a/contracts/external/cw-abc/src/commands.rs b/contracts/external/cw-abc/src/commands.rs index 80b90f2b8..1dd1d5ced 100644 --- a/contracts/external/cw-abc/src/commands.rs +++ b/contracts/external/cw-abc/src/commands.rs @@ -35,16 +35,10 @@ pub fn execute_buy(deps: DepsMut, _env: Env, info: MessageInfo) -> Result hatch_config.contribution_limits.max { + // Check contribution is within limits + if contribution < hatch_config.contribution_limits.min + || contribution > hatch_config.contribution_limits.max + { return Err(ContractError::ContributionLimit { min: hatch_config.contribution_limits.min, max: hatch_config.contribution_limits.max, @@ -267,7 +261,7 @@ fn calculate_exit_fee( // Ensure the exit fee is not greater than 100% ensure!( - exit_fee <= StdDecimal::percent(100), + exit_fee <= StdDecimal::one(), ContractError::InvalidExitFee {} ); diff --git a/contracts/external/cw-abc/src/contract.rs b/contracts/external/cw-abc/src/contract.rs index a67ab4e9f..edba2d055 100644 --- a/contracts/external/cw-abc/src/contract.rs +++ b/contracts/external/cw-abc/src/contract.rs @@ -9,14 +9,15 @@ use cw_tokenfactory_issuer::msg::{ DenomUnit, ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, Metadata, }; use cw_utils::parse_reply_instantiate_data; +use dao_interface::token::{InitialBalance, TokenInfo}; use crate::abc::{CommonsPhase, CurveFn}; use crate::curves::DecimalPlaces; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use crate::state::{ - CurveState, CURVE_STATE, CURVE_TYPE, FEES_RECIPIENT, HATCHER_ALLOWLIST, MAX_SUPPLY, PHASE, - PHASE_CONFIG, SUPPLY_DENOM, TOKEN_INSTANTIATION_INFO, TOKEN_ISSUER_CONTRACT, + CurveState, CURVE_STATE, CURVE_TYPE, FEES_RECIPIENT, HATCHER_ALLOWLIST, MAX_SUPPLY, + NEW_TOKEN_INFO, PHASE, PHASE_CONFIG, SUPPLY_DENOM, TOKEN_ISSUER_CONTRACT, }; use crate::{commands, queries}; @@ -42,31 +43,29 @@ pub fn instantiate( curve_type, phase_config, hatcher_allowlist, - token_issuer_code_id, } = msg; - if supply.subdenom.is_empty() { - return Err(ContractError::SupplyTokenError( - "Token subdenom must not be empty.".to_string(), - )); - } - phase_config.validate()?; // Validate and store the fees recipient FEES_RECIPIENT.save(deps.storage, &deps.api.addr_validate(&fees_recipient)?)?; - // Save new token info for use in reply - TOKEN_INSTANTIATION_INFO.save(deps.storage, &supply)?; + if let TokenInfo::New(new_token_info) = &supply.token_info { + if new_token_info.subdenom.is_empty() { + return Err(ContractError::SupplyTokenError( + "Token subdenom must not be empty.".to_string(), + )); + } + + // Save new token info for use in reply + NEW_TOKEN_INFO.save(deps.storage, new_token_info)?; + } 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); - let curve_state = CurveState::new(reserve.denom, normalization_places); - CURVE_STATE.save(deps.storage, &curve_state)?; + // Save the curve type CURVE_TYPE.save(deps.storage, &curve_type)?; if let Some(allowlist) = hatcher_allowlist { @@ -87,22 +86,69 @@ pub fn instantiate( // Initialize owner to sender cw_ownable::initialize_owner(deps.storage, deps.api, Some(info.sender.as_str()))?; - // Instantiate cw-token-factory-issuer contract - // Contract is immutable, no admin - let issuer_instantiate_msg = SubMsg::reply_always( - WasmMsg::Instantiate { - admin: None, - code_id: token_issuer_code_id, - msg: to_json_binary(&IssuerInstantiateMsg::NewToken { - subdenom: supply.subdenom.clone(), - })?, - funds: info.funds, - label: "cw-tokenfactory-issuer".to_string(), - }, - INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, - ); - - Ok(Response::default().add_submessage(issuer_instantiate_msg)) + // Setup the curve state + let normalization_places = DecimalPlaces::new(supply.decimals, reserve.decimals); + let mut curve_state = CurveState::new(reserve.denom, normalization_places); + + let msgs = match supply.token_info { + // Instantiate cw-token-factory-issuer contract if new + TokenInfo::New(new_token_info) => vec![SubMsg::reply_always( + WasmMsg::Instantiate { + // Contract is immutable, no admin + admin: None, + code_id: new_token_info.token_issuer_code_id, + msg: to_json_binary(&IssuerInstantiateMsg::NewToken { + subdenom: new_token_info.subdenom, + })?, + funds: info.funds, + label: "cw-tokenfactory-issuer".to_string(), + }, + INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID, + )], + TokenInfo::Existing { denom } => { + if !denom.starts_with("factory/") { + return Err(ContractError::SupplyTokenError( + "Token must be issued by the tokenfactory".to_string(), + )); + } + + // 'factory/' length is 8, so we trim that off + let issuer_subdenom = &denom[8..]; + + // Get a validated issuer from the expected [issuer]/[subdenom] string + let issuer = match issuer_subdenom.find('/') { + Some(end_index) => { + let issuer = deps.api.addr_validate(&issuer_subdenom[..end_index])?; + + // Set the existing supply on the curve state + let existing_supply = deps.querier.query_supply(&denom)?; + + if let Some(max_supply) = supply.max_supply { + if existing_supply.amount > max_supply { + return Err(ContractError::CannotExceedMaxSupply { max: max_supply }); + } + } + + curve_state.supply = existing_supply.amount; + + Ok(issuer) + } + None => Err(ContractError::SupplyTokenError( + "Tokenfactory denom did not contain a subdenom".to_string(), + )), + }?; + + TOKEN_ISSUER_CONTRACT.save(deps.storage, &issuer)?; + + vec![] + } + TokenInfo::Factory(_) => unimplemented!(), + }; + + // Save the curve state + CURVE_STATE.save(deps.storage, &curve_state)?; + + Ok(Response::default().add_submessages(msgs)) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -158,6 +204,9 @@ pub fn do_query(deps: Deps, _env: Env, msg: QueryMsg, curve_fn: CurveFn) -> StdR QueryMsg::Hatchers { start_after, limit } => { to_json_binary(&queries::query_hatchers(deps, start_after, limit)?) } + QueryMsg::HatcherAllowlist { start_after, limit } => { + to_json_binary(&queries::query_hatcher_allowlist(deps, start_after, limit)?) + } QueryMsg::MaxSupply {} => to_json_binary(&queries::query_max_supply(deps)?), QueryMsg::Ownership {} => to_json_binary(&cw_ownable::get_ownership(deps.storage)?), QueryMsg::PhaseConfig {} => to_json_binary(&queries::query_phase_config(deps)?), @@ -182,12 +231,12 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result Result max_supply { + return Err(ContractError::CannotExceedMaxSupply { max: max_supply }); + } + } + + // Grant an allowance to mint the initial supply + msgs.push(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_json_binary(&IssuerExecuteMsg::SetMinterAllowance { + address: env.contract.address.to_string(), + allowance: initial_supply, + })?, + funds: vec![], + }); + + // Call issuer contract to mint tokens for initial balances + new_token_info + .initial_balances + .iter() + .for_each(|b: &InitialBalance| { + msgs.push(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_json_binary(&IssuerExecuteMsg::Mint { + to_address: b.address.clone(), + amount: b.amount, + }) + .unwrap_or_default(), + funds: vec![], + }); + }); + + // Add initial DAO balance to initial_balances if nonzero. + if let Some(initial_dao_balance) = new_token_info.initial_dao_balance { + if !initial_dao_balance.is_zero() { + // In this case, it would be considered the fees recipient + let fees_recipient = FEES_RECIPIENT.load(deps.storage)?; + + msgs.push(WasmMsg::Execute { + contract_addr: issuer_addr.clone(), + msg: to_json_binary(&IssuerExecuteMsg::Mint { + to_address: fees_recipient.to_string(), + amount: initial_dao_balance, + })?, + funds: vec![], + }); + } + } + + CURVE_STATE.update(deps.storage, |mut x| -> StdResult<_> { + x.supply = initial_supply; + + Ok(x) + })?; + } + Ok(Response::new() .add_attribute("cw-tokenfactory-issuer-address", issuer_addr) .add_attribute("denom", denom) diff --git a/contracts/external/cw-abc/src/msg.rs b/contracts/external/cw-abc/src/msg.rs index a296b61cb..efcafd630 100644 --- a/contracts/external/cw-abc/src/msg.rs +++ b/contracts/external/cw-abc/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Decimal as StdDecimal, Uint128}; +use cosmwasm_std::{Addr, Decimal as StdDecimal, Empty, Uint128}; use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType, MinMax, ReserveToken, SupplyToken}; @@ -8,9 +8,6 @@ pub struct InstantiateMsg { /// The recipient for any fees collected from bonding curve operation pub fees_recipient: String, - /// The code id of the cw-tokenfactory-issuer contract - pub token_issuer_code_id: u64, - /// Supply token information pub supply: SupplyToken, @@ -120,6 +117,13 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + /// Lists the hatcher allowlist + /// Returns [`HatcherAllowlistResponse`] + #[returns(HatcherAllowlistResponse)] + HatcherAllowlist { + start_after: Option, + limit: Option, + }, /// Returns the Maximum Supply of the supply token #[returns(Uint128)] MaxSupply {}, @@ -144,7 +148,7 @@ pub struct CurveInfoResponse { /// The amount of tokens in the funding pool pub funding: Uint128, /// Current spot price of the token - pub spot_price: Decimal, + pub spot_price: StdDecimal, /// Current reserve denom pub reserve_denom: String, } @@ -157,7 +161,7 @@ pub struct DenomResponse { #[cw_serde] pub struct HatcherAllowlistResponse { /// Hatcher allowlist - pub allowlist: Option>, + pub allowlist: Option>, } #[cw_serde] diff --git a/contracts/external/cw-abc/src/queries.rs b/contracts/external/cw-abc/src/queries.rs index 8bc9138a6..dcd848d5a 100644 --- a/contracts/external/cw-abc/src/queries.rs +++ b/contracts/external/cw-abc/src/queries.rs @@ -1,10 +1,11 @@ use crate::abc::CurveFn; use crate::msg::{ CommonsPhaseConfigResponse, CurveInfoResponse, DenomResponse, DonationsResponse, - HatchersResponse, + HatcherAllowlistResponse, HatchersResponse, }; use crate::state::{ - CurveState, CURVE_STATE, DONATIONS, HATCHERS, MAX_SUPPLY, PHASE, PHASE_CONFIG, SUPPLY_DENOM, + CurveState, CURVE_STATE, DONATIONS, HATCHERS, HATCHER_ALLOWLIST, MAX_SUPPLY, PHASE, + PHASE_CONFIG, SUPPLY_DENOM, }; use cosmwasm_std::{Deps, Order, QuerierWrapper, StdResult, Uint128}; use std::ops::Deref; @@ -85,6 +86,36 @@ pub fn query_hatchers( Ok(HatchersResponse { hatchers }) } +/// Query hatcher allowlist +pub fn query_hatcher_allowlist( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + if HATCHER_ALLOWLIST.is_empty(deps.storage) { + return Ok(HatcherAllowlistResponse { allowlist: None }); + } + + let allowlist = cw_paginate_storage::paginate_map( + Deps { + storage: deps.storage, + api: deps.api, + querier: QuerierWrapper::new(deps.querier.deref()), + }, + &HATCHER_ALLOWLIST, + start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()? + .as_ref(), + limit, + Order::Descending, + )?; + + Ok(HatcherAllowlistResponse { + allowlist: Some(allowlist), + }) +} + /// Query the max supply of the supply token pub fn query_max_supply(deps: Deps) -> StdResult { let max_supply = MAX_SUPPLY.may_load(deps.storage)?; diff --git a/contracts/external/cw-abc/src/state.rs b/contracts/external/cw-abc/src/state.rs index cbda1b421..048fa9258 100644 --- a/contracts/external/cw-abc/src/state.rs +++ b/contracts/external/cw-abc/src/state.rs @@ -1,6 +1,7 @@ use cosmwasm_schema::cw_serde; +use dao_interface::token::NewTokenInfo; -use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType, SupplyToken}; +use crate::abc::{CommonsPhase, CommonsPhaseConfig, CurveType}; use cosmwasm_std::{Addr, Empty, Uint128}; use cw_storage_plus::{Item, Map}; @@ -65,8 +66,8 @@ 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"); +/// Temporarily holds NewTokenInfo when creating a new Token Factory denom +pub const NEW_TOKEN_INFO: Item = Item::new("new_token_info"); /// The address of the cw-tokenfactory-issuer contract pub const TOKEN_ISSUER_CONTRACT: Item = Item::new("token_issuer_contract"); 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 7f88f5a5d..1b43bb9ca 100644 --- a/contracts/external/cw-abc/src/test_tube/integration_tests.rs +++ b/contracts/external/cw-abc/src/test_tube/integration_tests.rs @@ -14,6 +14,7 @@ use super::test_env::{TestEnv, TestEnvBuilder, DENOM, RESERVE}; use cosmwasm_std::{coins, Decimal, Uint128}; use cw_tokenfactory_issuer::msg::QueryMsg as IssuerQueryMsg; +use dao_interface::token::{NewTokenInfo, TokenInfo}; use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest; use osmosis_test_tube::{osmosis_std::types::cosmos::base::v1beta1::Coin, Account, OsmosisTestApp}; @@ -281,10 +282,14 @@ fn test_allowlist() { let builder = TestEnvBuilder::new(); let instantiate_msg = InstantiateMsg { fees_recipient: "replaced to accounts[0]".to_string(), - token_issuer_code_id: 0, supply: SupplyToken { - subdenom: DENOM.to_string(), - metadata: None, + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: 0, + subdenom: DENOM.to_string(), + metadata: None, + initial_balances: vec![], + initial_dao_balance: None, + }), decimals: 6, max_supply: Some(Uint128::from(1000000000u128)), }, @@ -514,3 +519,151 @@ fn test_update_curve() { }) ); } + +#[test] +fn test_existing_token_failures() { + let app = OsmosisTestApp::new(); + let builder = TestEnvBuilder::new(); + + // The tokenfactory token does not exist - fails in supply query + let mut instantiate_msg = InstantiateMsg { + fees_recipient: "replaced to accounts[0]".to_string(), + supply: SupplyToken { + token_info: TokenInfo::Existing { + denom: "factory/address/nonexistent".to_string(), + }, + decimals: 6, + max_supply: Some(Uint128::from(1000000000u128)), + }, + reserve: ReserveToken { + denom: RESERVE.to_string(), + decimals: 6, + }, + phase_config: CommonsPhaseConfig { + hatch: HatchConfig { + contribution_limits: MinMax { + min: Uint128::from(10u128), + max: Uint128::from(1000000u128), + }, + initial_raise: MinMax { + min: Uint128::from(10u128), + max: Uint128::from(1000000u128), + }, + entry_fee: Decimal::percent(10u64), + exit_fee: Decimal::percent(10u64), + }, + open: OpenConfig { + entry_fee: Decimal::percent(10u64), + exit_fee: Decimal::percent(10u64), + }, + closed: ClosedConfig {}, + }, + hatcher_allowlist: None, + curve_type: CurveType::Constant { + value: Uint128::one(), + scale: 1, + }, + }; + let result = builder.setup(&app, instantiate_msg.clone()); + assert!(result.is_err()); + + // A subdenom is required + let builder = TestEnvBuilder::new(); + instantiate_msg.supply.token_info = TokenInfo::Existing { + denom: "factory/malformed".to_string(), + }; + let result = builder.setup(&app, instantiate_msg.clone()); + assert!(result.is_err()); + + // Only tokenfactory tokens are supported + let builder = TestEnvBuilder::new(); + instantiate_msg.supply.token_info = TokenInfo::Existing { + denom: "junox".to_string(), + }; + let result = builder.setup(&app, instantiate_msg.clone()); + assert!(result.is_err()); +} + +#[test] +fn test_existing_token() { + let app = OsmosisTestApp::new(); + let builder = TestEnvBuilder::new(); + + let result = builder.setup_with_token( + &app, + InstantiateMsg { + fees_recipient: "replaced to accounts[0]".to_string(), + supply: SupplyToken { + token_info: TokenInfo::Existing { + denom: "replaced".to_string(), + }, + decimals: 6, + max_supply: None, + }, + reserve: ReserveToken { + denom: RESERVE.to_string(), + decimals: 6, + }, + phase_config: CommonsPhaseConfig { + hatch: HatchConfig { + contribution_limits: MinMax { + min: Uint128::from(10u128), + max: Uint128::from(1000000u128), + }, + initial_raise: MinMax { + min: Uint128::from(10u128), + max: Uint128::from(1000000u128), + }, + entry_fee: Decimal::percent(10u64), + exit_fee: Decimal::percent(10u64), + }, + open: OpenConfig { + entry_fee: Decimal::percent(10u64), + exit_fee: Decimal::percent(10u64), + }, + closed: ClosedConfig {}, + }, + hatcher_allowlist: None, + curve_type: CurveType::Constant { + value: Uint128::one(), + scale: 1, + }, + }, + ); + assert!(result.is_ok()); + let TestEnv { + ref abc, + ref accounts, + ref tf_issuer, + .. + } = result.unwrap(); + + let denom_response: DenomResponse = tf_issuer + .query(&cw_tokenfactory_issuer::msg::QueryMsg::Denom {}) + .unwrap(); + let _denom = format!( + "factory/{}/{}", + tf_issuer.contract_addr, denom_response.denom + ); + + // Allow the ABC to modify the supply + let result = tf_issuer.execute( + &cw_tokenfactory_issuer::msg::ExecuteMsg::SetMinterAllowance { + address: abc.contract_addr.clone(), + allowance: Uint128::MAX, + }, + &[], + &accounts[0], + ); + assert!(result.is_ok()); + + let result = tf_issuer.execute( + &cw_tokenfactory_issuer::msg::ExecuteMsg::SetBurnerAllowance { + address: abc.contract_addr.clone(), + allowance: Uint128::MAX, + }, + &[], + &accounts[0], + ); + assert!(result.is_ok()); +} 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 28dec2752..b0eb95b67 100644 --- a/contracts/external/cw-abc/src/test_tube/test_env.rs +++ b/contracts/external/cw-abc/src/test_tube/test_env.rs @@ -12,6 +12,7 @@ use crate::{ }; use cosmwasm_std::{Coin, Decimal, Uint128}; +use dao_interface::token::{NewTokenInfo, TokenInfo}; use dao_testing::test_tube::cw_tokenfactory_issuer::TokenfactoryIssuer; use osmosis_test_tube::{ osmosis_std::types::cosmos::bank::v1beta1::QueryAllBalancesRequest, @@ -106,10 +107,14 @@ impl TestEnvBuilder { app, &InstantiateMsg { fees_recipient: accounts[0].address(), - token_issuer_code_id: issuer_id, supply: SupplyToken { - subdenom: DENOM.to_string(), - metadata: None, + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, + subdenom: DENOM.to_string(), + metadata: None, + initial_balances: vec![], + initial_dao_balance: None, + }), decimals: 6, max_supply: Some(Uint128::from(1000000000u128)), }, @@ -170,7 +175,10 @@ impl TestEnvBuilder { let issuer_id = TokenfactoryIssuer::upload(app, &accounts[0])?; // Override issuer_id and fees_recipient - msg.token_issuer_code_id = issuer_id; + if let TokenInfo::New(ref mut new_token_info) = msg.supply.token_info { + new_token_info.token_issuer_code_id = issuer_id; + } + msg.fees_recipient = accounts[0].address(); let abc = CwAbc::deploy(app, &msg, &accounts[0])?; @@ -187,6 +195,57 @@ impl TestEnvBuilder { }) } + pub fn setup_with_token( + self, + app: &'_ OsmosisTestApp, + mut msg: InstantiateMsg, + ) -> Result, RunnerError> { + let accounts = app + .init_accounts(&[Coin::new(1000000000000000u128, RESERVE)], 10) + .unwrap(); + + let tf_issuer: TokenfactoryIssuer = TokenfactoryIssuer::new( + app, + &cw_tokenfactory_issuer::msg::InstantiateMsg::NewToken { + subdenom: "subdenom".to_string(), + }, + &accounts[0], + )?; + + msg.fees_recipient = accounts[0].address(); + + msg.supply.token_info = TokenInfo::Existing { + denom: format!("factory/{}/{}", tf_issuer.contract_addr, "subdenom"), + }; + + // Accounts[0] has minted some tokens to themselves + tf_issuer.execute( + &cw_tokenfactory_issuer::msg::ExecuteMsg::SetMinterAllowance { + address: accounts[0].address().to_string(), + allowance: Uint128::MAX, + }, + &[], + &accounts[0], + )?; + tf_issuer.execute( + &cw_tokenfactory_issuer::msg::ExecuteMsg::Mint { + to_address: accounts[0].address().to_string(), + amount: Uint128::new(1_000_000u128), + }, + &[], + &accounts[0], + )?; + + let abc = CwAbc::deploy(app, &msg, &accounts[0])?; + + Ok(TestEnv { + app, + abc, + tf_issuer, + accounts, + }) + } + pub fn upload_issuer(self, app: &'_ OsmosisTestApp, signer: &SigningAccount) -> u64 { TokenfactoryIssuer::upload(app, signer).unwrap() } diff --git a/contracts/external/cw-abc/src/testing.rs b/contracts/external/cw-abc/src/testing.rs index b324ca8ed..29adc1bfd 100644 --- a/contracts/external/cw-abc/src/testing.rs +++ b/contracts/external/cw-abc/src/testing.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{ testing::{mock_env, mock_info}, Decimal, DepsMut, Response, Uint128, }; -use dao_interface::token::NewDenomMetadata; +use dao_interface::token::{NewDenomMetadata, NewTokenInfo, TokenInfo}; use crate::contract; use crate::msg::InstantiateMsg; @@ -43,10 +43,14 @@ pub fn default_instantiate_msg( ) -> InstantiateMsg { InstantiateMsg { fees_recipient: TEST_CREATOR.to_string(), - token_issuer_code_id: 1, supply: SupplyToken { - subdenom: TEST_SUPPLY_DENOM.to_string(), - metadata: Some(default_supply_metadata()), + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: 1, + subdenom: TEST_SUPPLY_DENOM.to_string(), + metadata: Some(default_supply_metadata()), + initial_balances: vec![], + initial_dao_balance: None, + }), decimals, max_supply: None, }, diff --git a/contracts/external/cw-tokenfactory-issuer/src/msg.rs b/contracts/external/cw-tokenfactory-issuer/src/msg.rs index 4587a0ea5..324b5d8ed 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/msg.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/msg.rs @@ -11,11 +11,11 @@ pub enum InstantiateMsg { /// Newly created token will have full denom as `factory//`. /// It will be attached to the contract setup the beforesend listener automatically. NewToken { - /// component of fulldenom (`factory//`). + /// component of full denom (`factory//`). subdenom: String, }, /// `ExistingToken` will use already created token. So to set this up, - /// Token Factory admin for the existing token needs trasfer admin over + /// Token Factory admin for the existing token needs transfer admin over /// to this contract, and optionally set the `BeforeSendHook` manually. ExistingToken { denom: String }, } @@ -23,25 +23,25 @@ pub enum InstantiateMsg { /// State changing methods available to this smart contract. #[cw_serde] pub enum ExecuteMsg { - /// Allow adds the target address to the allowlist to be able to send or recieve tokens even if the token + /// Allow adds the target address to the allowlist to be able to send or receive tokens even if the token /// is frozen. Token Factory's BeforeSendHook listener must be set to this contract in order for this feature /// to work. /// - /// This functionality is intedended for DAOs who do not wish to have a their tokens liquid while bootstrapping + /// This functionality is intended for DAOs who do not wish to have a their tokens liquid while bootstrapping /// their DAO. For example, a DAO may wish to white list a Token Staking contract (to allow users to stake their /// tokens in the DAO) or a Merkle Drop contract (to allow users to claim their tokens). Allow { address: String, status: bool }, - /// Burn token to address. Burn allowance is required and wiil be deducted after successful burn. + /// Burn token to address. Burn allowance is required and will be deducted after successful burn. Burn { from_address: String, amount: Uint128, }, - /// Mint token to address. Mint allowance is required and wiil be deducted after successful mint. + /// Mint token to address. Mint allowance is required and will be deducted after successful mint. Mint { to_address: String, amount: Uint128 }, - /// Deny adds the target address to the denylist, whis prevents them from sending/receiving the token attached + /// Deny adds the target address to the denylist, which prevents them from sending/receiving the token attached /// to this contract tokenfactory's BeforeSendHook listener must be set to this contract in order for this /// feature to work as intended. Deny { address: String, status: bool }, @@ -123,7 +123,7 @@ pub enum QueryMsg { #[returns(AllowanceResponse)] BurnAllowance { address: String }, - /// Enumerates over all burn allownances. Response: AllowancesResponse + /// Enumerates over all burn allowances. Response: AllowancesResponse #[returns(AllowancesResponse)] BurnAllowances { start_after: Option, @@ -134,7 +134,7 @@ pub enum QueryMsg { #[returns(AllowanceResponse)] MintAllowance { address: String }, - /// Enumerates over all mint allownances. Response: AllowancesResponse + /// Enumerates over all mint allowances. Response: AllowancesResponse #[returns(AllowancesResponse)] MintAllowances { start_after: Option, @@ -202,7 +202,7 @@ pub struct DenomResponse { } /// Returns the current owner of this issuer contract who is allowed to -/// call priviledged methods. +/// call privileged methods. #[cw_serde] pub struct OwnerResponse { pub address: String, diff --git a/contracts/external/cw-tokenfactory-issuer/src/queries.rs b/contracts/external/cw-tokenfactory-issuer/src/queries.rs index a6b7f3b5d..6e8025c55 100644 --- a/contracts/external/cw-tokenfactory-issuer/src/queries.rs +++ b/contracts/external/cw-tokenfactory-issuer/src/queries.rs @@ -79,7 +79,7 @@ pub fn query_allowances( .collect() } -/// Enumerates over all allownances. Response: AllowancesResponse +/// Enumerates over all allowances. Response: AllowancesResponse pub fn query_mint_allowances( deps: Deps, start_after: Option, @@ -90,7 +90,7 @@ pub fn query_mint_allowances( }) } -/// Enumerates over all burn allownances. Response: AllowancesResponse +/// Enumerates over all burn allowances. Response: AllowancesResponse pub fn query_burn_allowances( deps: Deps, start_after: Option, diff --git a/contracts/external/dao-abc-factory/src/test_tube/test_env.rs b/contracts/external/dao-abc-factory/src/test_tube/test_env.rs index a31a88822..03fe3fa9c 100644 --- a/contracts/external/dao-abc-factory/src/test_tube/test_env.rs +++ b/contracts/external/dao-abc-factory/src/test_tube/test_env.rs @@ -16,11 +16,12 @@ use cw_utils::Duration; use dao_interface::{ msg::QueryMsg as DaoQueryMsg, state::{Admin, ModuleInstantiateInfo, ProposalModule}, + token::{NewTokenInfo, TokenInfo}, }; use dao_voting::{ pre_propose::PreProposeInfo, threshold::PercentageThreshold, threshold::Threshold, }; -use dao_voting_token_staked::msg::{QueryMsg as TokenVotingQueryMsg, TokenInfo}; +use dao_voting_token_staked::msg::QueryMsg as TokenVotingQueryMsg; use dao_testing::test_tube::{ cw_abc::CwAbc, cw_tokenfactory_issuer::TokenfactoryIssuer, dao_dao_core::DaoCore, @@ -136,10 +137,16 @@ impl TestEnvBuilder { msg: to_json_binary(&ExecuteMsg::AbcFactory { instantiate_msg: cw_abc::msg::InstantiateMsg { fees_recipient: accounts[0].address(), - token_issuer_code_id: issuer_id, + supply: SupplyToken { - subdenom: DENOM.to_string(), - metadata: None, + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, + subdenom: DENOM.to_string(), + metadata: None, + initial_balances: vec![], + initial_dao_balance: None, + }), + decimals: 6, max_supply: Some(Uint128::from(1000000000u128)), }, @@ -248,10 +255,14 @@ impl TestEnvBuilder { msg: to_json_binary(&ExecuteMsg::AbcFactory { instantiate_msg: cw_abc::msg::InstantiateMsg { fees_recipient: accounts[0].address(), - token_issuer_code_id: issuer_id, supply: SupplyToken { - subdenom: DENOM.to_string(), - metadata: None, + token_info: TokenInfo::New(NewTokenInfo { + token_issuer_code_id: issuer_id, + subdenom: DENOM.to_string(), + metadata: None, + initial_balances: vec![], + initial_dao_balance: None, + }), decimals: 6, max_supply: Some(Uint128::from(1000000000u128)), }, diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 8fd7e0c95..2eb1458bb 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -272,7 +272,7 @@ pub fn instantiate_with_native_staked_balances_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, msg: to_json_binary(&dao_voting_token_staked::msg::InstantiateMsg { - token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + token_info: dao_interface::token::TokenInfo::Existing { denom: "ujuno".to_string(), }, unstaking_duration: None, diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 020154700..ef6fecc96 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -270,7 +270,7 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( voting_module_instantiate_info: ModuleInstantiateInfo { code_id: native_stake_id, msg: to_json_binary(&dao_voting_token_staked::msg::InstantiateMsg { - token_info: dao_voting_token_staked::msg::TokenInfo::Existing { + token_info: dao_interface::token::TokenInfo::Existing { denom: "ujuno".to_string(), }, unstaking_duration: None, diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index a8d1e2d6d..72429742a 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -17,7 +17,7 @@ use cw_utils::{ use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::{ state::ModuleInstantiateCallback, - token::{InitialBalance, NewTokenInfo, TokenFactoryCallback}, + token::{InitialBalance, NewTokenInfo, TokenFactoryCallback, TokenInfo}, voting::{ DenomResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }, @@ -33,7 +33,7 @@ use dao_voting::{ use crate::error::ContractError; use crate::msg::{ ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - StakerBalanceResponse, TokenInfo, + StakerBalanceResponse, }; use crate::state::{ Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, DENOM, HOOKS, MAX_CLAIMS, STAKED_BALANCES, @@ -135,10 +135,10 @@ pub fn instantiate( funds, } => { // Call factory contract. Use only a trusted factory contract, - // as this is a critical security component and valdiation of + // as this is a critical security component and validation of // setup will happen in the factory. Ok(Response::new() - .add_attribute("action", "intantiate") + .add_attribute("action", "instantiate") .add_attribute("token", "custom_factory") .add_submessage(SubMsg::reply_on_success( WasmMsg::Execute { diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index 98809ec9e..f0cef3fb3 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -1,29 +1,10 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Binary, Uint128}; +use cosmwasm_std::Uint128; use cw_utils::Duration; use dao_dao_macros::{active_query, native_token_query, voting_module_query}; -use dao_interface::token::NewTokenInfo; +use dao_interface::token::TokenInfo; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; -#[cw_serde] -pub enum TokenInfo { - /// Uses an existing Token Factory token and creates a new issuer contract. - /// Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, - /// must be done manually. - Existing { - /// Token factory denom - denom: String, - }, - /// Creates a new Token Factory token via the issue contract with the DAO automatically - /// setup as admin and owner. - New(NewTokenInfo), - /// Uses a factory contract that must return the denom, optionally a Token Contract address. - /// The binary must serialize to a `WasmMsg::Execute` message. - /// Validation happens in the factory contract itself, so be sure to use a - /// trusted factory contract. - Factory(Binary), -} - #[cw_serde] pub struct InstantiateMsg { /// New or existing native token to use for voting power. diff --git a/contracts/voting/dao-voting-token-staked/src/state.rs b/contracts/voting/dao-voting-token-staked/src/state.rs index 6fb8bc4a0..155c16513 100644 --- a/contracts/voting/dao-voting-token-staked/src/state.rs +++ b/contracts/voting/dao-voting-token-staked/src/state.rs @@ -4,10 +4,9 @@ use cw_controllers::Claims; use cw_hooks::Hooks; use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; use cw_utils::Duration; +use dao_interface::token::TokenInfo; use dao_voting::threshold::ActiveThreshold; -use crate::msg::TokenInfo; - #[cw_serde] pub struct Config { pub unstaking_duration: Option, diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index 90b4fab28..437d4da3a 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -1,7 +1,7 @@ use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; use crate::msg::{ ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, - StakerBalanceResponse, TokenInfo, + StakerBalanceResponse, }; use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; @@ -11,6 +11,7 @@ use cw_multi_test::{ next_block, App, AppResponse, BankSudo, Contract, ContractWrapper, Executor, SudoMsg, }; use cw_utils::Duration; +use dao_interface::token::TokenInfo; use dao_interface::voting::{ DenomResponse, InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs index 926abe763..2fcdb65e2 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/integration_tests.rs @@ -5,7 +5,7 @@ use cw_utils::Duration; use dao_interface::{ msg::QueryMsg as DaoQueryMsg, state::{Admin, ModuleInstantiateInfo}, - token::{InitialBalance, NewDenomMetadata, NewTokenInfo}, + token::{InitialBalance, NewDenomMetadata, NewTokenInfo, TokenInfo}, }; use dao_testing::test_tube::{cw_tokenfactory_issuer::TokenfactoryIssuer, dao_dao_core::DaoCore}; use dao_voting::{ @@ -18,7 +18,7 @@ use osmosis_test_tube::{ }; use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenInfo}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, tests::test_tube::test_env::TokenVotingContract, ContractError, }; diff --git a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs index 3c93c9ee6..84dd18195 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/test_tube/test_env.rs @@ -3,7 +3,7 @@ #![allow(dead_code)] use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg, TokenInfo}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, ContractError, }; @@ -13,7 +13,7 @@ use cw_utils::Duration; use dao_interface::{ msg::QueryMsg as DaoQueryMsg, state::{Admin, ModuleInstantiateInfo, ProposalModule}, - token::{InitialBalance, NewDenomMetadata, NewTokenInfo}, + token::{InitialBalance, NewDenomMetadata, NewTokenInfo, TokenInfo}, voting::{IsActiveResponse, VotingPowerAtHeightResponse}, }; use dao_voting::{ diff --git a/packages/dao-interface/src/token.rs b/packages/dao-interface/src/token.rs index c735a15c4..3a760d6aa 100644 --- a/packages/dao-interface/src/token.rs +++ b/packages/dao-interface/src/token.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Binary, Uint128}; // These are Cosmos Proto types used for Denom Metadata. // We re-export them here for convenience. @@ -7,6 +7,25 @@ pub use osmosis_std::types::cosmos::bank::v1beta1::{DenomUnit, Metadata}; use crate::state::ModuleInstantiateCallback; +#[cw_serde] +pub enum TokenInfo { + /// Uses an existing Token Factory token and creates a new issuer contract. + /// Full setup, such as transferring ownership or setting up MsgSetBeforeSendHook, + /// must be done manually. + Existing { + /// Token factory denom + denom: String, + }, + /// Creates a new Token Factory token via the issue contract with the DAO automatically + /// setup as admin and owner. + New(NewTokenInfo), + /// Uses a factory contract that must return the denom, optionally a Token Contract address. + /// The binary must serialize to a `WasmMsg::Execute` message. + /// Validation happens in the factory contract itself, so be sure to use a + /// trusted factory contract. + Factory(Binary), +} + #[cw_serde] pub struct InitialBalance { pub amount: Uint128, diff --git a/packages/dao-testing/src/test_tube/mod.rs b/packages/dao-testing/src/test_tube/mod.rs index e609fa47e..f7e1cb743 100644 --- a/packages/dao-testing/src/test_tube/mod.rs +++ b/packages/dao-testing/src/test_tube/mod.rs @@ -2,7 +2,7 @@ // and also, tarpaulin will not be able read coverage out of wasm binary anyway #![cfg(not(tarpaulin))] -// Integrationg tests using an actual chain binary, requires +// Integration tests using an actual chain binary, requires // the "test-tube" feature to be enabled // cargo test --features test-tube #[cfg(feature = "test-tube")]