From 1eb197aac8711dc2df7434ffc206d03747821241 Mon Sep 17 00:00:00 2001 From: Jake Hartnell Date: Tue, 12 Sep 2023 17:01:00 -0700 Subject: [PATCH] Start outlining factory pattern work --- Cargo.lock | 15 + Cargo.toml | 1 + ci/bootstrap-env/src/main.rs | 3 + ci/integration-tests/src/helpers/helper.rs | 3 + contracts/dao-dao-core/src/contract.rs | 1 + .../dao-voting-cw721-staked/src/contract.rs | 37 +- .../dao-voting-cw721-staked/src/error.rs | 7 + .../voting/dao-voting-cw721-staked/src/msg.rs | 35 +- .../dao-voting-token-staked/src/contract.rs | 41 +- .../dao-voting-token-staked/src/error.rs | 3 + .../voting/dao-voting-token-staked/src/msg.rs | 5 +- packages/dao-interface/src/state.rs | 9 +- packages/dao-testing/src/helpers.rs | 8 + .../dao-test-custom-factory/.cargo/config | 4 + .../dao-test-custom-factory/.gitignore | 15 + .../dao-test-custom-factory/Cargo.toml | 30 ++ test-contracts/dao-test-custom-factory/README | 2 + .../examples/schema.rs | 10 + .../schema/admin_response.json | 6 + .../schema/dao_response.json | 6 + .../schema/execute_msg.json | 487 ++++++++++++++++++ .../schema/info_response.json | 32 ++ .../schema/instantiate_msg.json | 13 + .../schema/query_msg.json | 42 ++ .../dao-test-custom-factory/src/contract.rs | 91 ++++ .../dao-test-custom-factory/src/error.rs | 14 + .../dao-test-custom-factory/src/lib.rs | 5 + .../dao-test-custom-factory/src/msg.rs | 17 + 28 files changed, 919 insertions(+), 23 deletions(-) create mode 100644 test-contracts/dao-test-custom-factory/.cargo/config create mode 100644 test-contracts/dao-test-custom-factory/.gitignore create mode 100644 test-contracts/dao-test-custom-factory/Cargo.toml create mode 100644 test-contracts/dao-test-custom-factory/README create mode 100644 test-contracts/dao-test-custom-factory/examples/schema.rs create mode 100644 test-contracts/dao-test-custom-factory/schema/admin_response.json create mode 100644 test-contracts/dao-test-custom-factory/schema/dao_response.json create mode 100644 test-contracts/dao-test-custom-factory/schema/execute_msg.json create mode 100644 test-contracts/dao-test-custom-factory/schema/info_response.json create mode 100644 test-contracts/dao-test-custom-factory/schema/instantiate_msg.json create mode 100644 test-contracts/dao-test-custom-factory/schema/query_msg.json create mode 100644 test-contracts/dao-test-custom-factory/src/contract.rs create mode 100644 test-contracts/dao-test-custom-factory/src/error.rs create mode 100644 test-contracts/dao-test-custom-factory/src/lib.rs create mode 100644 test-contracts/dao-test-custom-factory/src/msg.rs diff --git a/Cargo.lock b/Cargo.lock index b81b4604b..3cb7af2a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1963,6 +1963,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "dao-test-custom-factory" +version = "2.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "dao-dao-macros", + "dao-interface", + "thiserror", +] + [[package]] name = "dao-testing" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index 0d55ba587..505d3739b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", v dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.2.0" } dao-proposal-sudo = { path = "./test-contracts/dao-proposal-sudo", version = "2.2.0" } dao-proposal-hook-counter = { path = "./test-contracts/dao-proposal-hook-counter", version = "2.2.0" } +dao-test-custom-factory = { path = "./test-contracts/dao-test-custom-factory", version = "*" } dao-testing = { path = "./packages/dao-testing", version = "2.2.0" } dao-voting = { path = "./packages/dao-voting", version = "2.2.0" } dao-voting-cw20-balance = { path = "./test-contracts/dao-voting-cw20-balance", version = "2.2.0" } diff --git a/ci/bootstrap-env/src/main.rs b/ci/bootstrap-env/src/main.rs index b8bdeb0a5..85fd119a5 100644 --- a/ci/bootstrap-env/src/main.rs +++ b/ci/bootstrap-env/src/main.rs @@ -73,6 +73,7 @@ fn main() -> Result<()> { }, active_threshold: None, })?, + funds: vec![], admin: Some(Admin::CoreModule {}), label: "DAO DAO Voting Module".to_string(), }, @@ -101,12 +102,14 @@ fn main() -> Result<()> { }) .unwrap(), admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO Pre-Propose Module".to_string(), }, }, close_proposal_on_execution_failure: false, })?, admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO Proposal Module".to_string(), }], initial_items: None, diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs index 4d30c1670..bcfd0e26d 100644 --- a/ci/integration-tests/src/helpers/helper.rs +++ b/ci/integration-tests/src/helpers/helper.rs @@ -59,6 +59,7 @@ pub fn create_dao( })?, admin: Some(Admin::CoreModule {}), label: "DAO DAO Voting Module".to_string(), + funds: vec![], }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { code_id: chain.orc.contract_map.code_id("dao_proposal_single")?, @@ -86,11 +87,13 @@ pub fn create_dao( }) .unwrap(), admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO Pre-Propose Module".to_string(), }, }, })?, admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO Proposal Module".to_string(), }], initial_items: None, diff --git a/contracts/dao-dao-core/src/contract.rs b/contracts/dao-dao-core/src/contract.rs index 97f3a7d3e..21e38784e 100644 --- a/contracts/dao-dao-core/src/contract.rs +++ b/contracts/dao-dao-core/src/contract.rs @@ -910,6 +910,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result match from_binary(&binary)? { + WasmMsg::Execute { + msg, + contract_addr, + funds, + } => Ok(Response::new() + .add_attribute("action", "intantiate") + .add_submessage(SubMsg::reply_on_success( + WasmMsg::Execute { + contract_addr, + msg, + // TODO what to do with funds for fair burn? + // Need to pass them along to the factory + funds, + }, + FACTORY_EXECUTE_REPLY_ID, + ))), + _ => return Err(ContractError::UnsupportedFactoryMsg {}), + }, } } @@ -713,6 +733,21 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + let res = parse_reply_execute_data(msg)?; + + // TODO validate active threshold is set. Some contracts such as a minter, + // contract may not have any supply until tokens are minted. + + match res.data { + Some(data) => { + // TODO parse data and save token contract address / denom + unimplemented!() + } + // TODO better error + None => return Err(ContractError::Unauthorized {}), + } + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index 15e2cecea..595336e97 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Addr, StdError}; +use cw_utils::ParseReplyError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -6,6 +7,9 @@ pub enum ContractError { #[error(transparent)] Std(#[from] StdError), + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), + #[error("Can not stake that which has already been staked")] AlreadyStaked {}, @@ -45,6 +49,9 @@ pub enum ContractError { #[error("Got a submessage reply with unknown id: {id}")] UnknownReplyId { id: u64 }, + #[error("Factory message must serialize to WasmMsg::Execute")] + UnsupportedFactoryMsg {}, + #[error("Active threshold count must be greater than zero")] ZeroActiveCount {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index f47d82533..7fb38d79d 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -8,10 +8,12 @@ use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; #[cw_serde] #[allow(clippy::large_enum_variant)] pub enum NftContract { + /// Uses an existing cw721 or sg721 token contract. Existing { - /// Address of an already instantiated cw721 token contract. + /// Address of an already instantiated cw721 or sg721 token contract. address: String, }, + /// Creates a new NFT collection used for staking and governance. New { /// Code ID for cw721 token contract. code_id: u64, @@ -23,6 +25,9 @@ pub enum NftContract { /// valid mint message for the corresponding cw721 contract. initial_nfts: Vec, }, + /// Uses a factory pattern that must return the denom, optionally a Token Contract address, + /// and any setup messages. The binary must serialize to a `WasmMsg::Execute` message. + Factory(Binary), } #[cw_serde] @@ -46,22 +51,20 @@ pub enum ExecuteMsg { /// Unstakes the specified token_ids on behalf of the /// sender. token_ids must have unique values and have non-zero /// length. - Unstake { - token_ids: Vec, - }, + Unstake { token_ids: Vec }, + /// Claim NFTs that have been unstaked for the specified duration. ClaimNfts {}, - UpdateConfig { - duration: Option, - }, - AddHook { - addr: String, - }, - RemoveHook { - addr: String, - }, - /// Sets the active threshold to a new value. Only the - /// instantiator this contract (a DAO most likely) may call this - /// method. + /// Updates the contract configuration, namely unstaking duration. + /// Only callable by the DAO that initialized this voting contract. + UpdateConfig { duration: Option }, + /// Adds a hook which is called on staking / unstaking events. + /// Only callable by the DAO that initialized this voting contract. + AddHook { addr: String }, + /// Removes a hook which is called on staking / unstaking events. + /// Only callable by the DAO that initialized this voting contract. + RemoveHook { addr: String }, + /// Sets the active threshold to a new value. + /// Only callable by the DAO that initialized this voting contract. UpdateActiveThreshold { new_threshold: Option, }, diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index b9d692348..d47c164fe 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -2,8 +2,8 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ - coins, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, + coins, from_binary, to_binary, BankMsg, BankQuery, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, + MessageInfo, Order, Reply, Response, StdResult, SubMsg, Uint128, Uint256, WasmMsg, }; use cw2::{get_contract_version, set_contract_version, ContractVersion}; use cw_controllers::ClaimsResponse; @@ -11,7 +11,9 @@ use cw_storage_plus::Bound; use cw_tokenfactory_issuer::msg::{ DenomUnit, ExecuteMsg as IssuerExecuteMsg, InstantiateMsg as IssuerInstantiateMsg, Metadata, }; -use cw_utils::{maybe_addr, must_pay, parse_reply_instantiate_data, Duration}; +use cw_utils::{ + maybe_addr, must_pay, parse_reply_execute_data, parse_reply_instantiate_data, Duration, +}; use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::state::ModuleInstantiateCallback; use dao_interface::voting::{ @@ -43,6 +45,7 @@ const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; const INSTANTIATE_TOKEN_FACTORY_ISSUER_REPLY_ID: u64 = 0; +const FACTORY_EXECUTE_REPLY_ID: u64 = 2; // We multiply by this when calculating needed power for being active // when using active threshold with percent @@ -122,6 +125,23 @@ pub fn instantiate( .add_attribute("token", "new_token") .add_submessage(issuer_instantiate_msg)) } + TokenInfo::Factory(binary) => match from_binary(&binary)? { + WasmMsg::Execute { + msg, contract_addr, .. + } => Ok(Response::new() + .add_attribute("action", "intantiate") + .add_submessage(SubMsg::reply_on_success( + WasmMsg::Execute { + contract_addr, + msg, + // TODO what to do with funds for fair burn? + // Need to pass them along to the factory + funds: vec![], + }, + FACTORY_EXECUTE_REPLY_ID, + ))), + _ => return Err(ContractError::UnsupportedFactoryMsg {}), + }, } } @@ -695,6 +715,21 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result unreachable!(), } } + FACTORY_EXECUTE_REPLY_ID => { + let res = parse_reply_execute_data(msg)?; + + // TODO validate active threshold is set. Some contracts such as a minter, + // contract may not have any supply until tokens are minted. + + match res.data { + Some(data) => { + // TODO parse data and save token contract address / denom + unimplemented!() + } + // TODO better error + None => return Err(ContractError::Unauthorized {}), + } + } _ => Err(ContractError::UnknownReplyId { id: msg.id }), } } diff --git a/contracts/voting/dao-voting-token-staked/src/error.rs b/contracts/voting/dao-voting-token-staked/src/error.rs index 27bea213a..5239f3bd1 100644 --- a/contracts/voting/dao-voting-token-staked/src/error.rs +++ b/contracts/voting/dao-voting-token-staked/src/error.rs @@ -41,6 +41,9 @@ pub enum ContractError { #[error("Got a submessage reply with unknown id: {id}")] UnknownReplyId { id: u64 }, + #[error("Factory message must serialize to WasmMsg::Execute")] + UnsupportedFactoryMsg {}, + #[error("Amount being unstaked must be non-zero")] ZeroUnstake {}, } diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index e08123680..6d0581902 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Binary, Uint128}; use cw_tokenfactory_issuer::msg::DenomUnit; use cw_utils::Duration; use dao_dao_macros::{active_query, token_query, voting_module_query}; @@ -54,6 +54,9 @@ pub enum TokenInfo { /// Creates a new Token Factory token via the issue contract with the DAO automatically /// setup as admin and owner. New(NewTokenInfo), + /// Uses a factory pattern that must return the denom, optionally a Token Contract address. + /// The binary must serialize to a `WasmMsg::Execute` message. + Factory(Binary), } #[cw_serde] diff --git a/packages/dao-interface/src/state.rs b/packages/dao-interface/src/state.rs index 3a0841f9b..0bd445de3 100644 --- a/packages/dao-interface/src/state.rs +++ b/packages/dao-interface/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Binary, CosmosMsg, WasmMsg}; +use cosmwasm_std::{Addr, Binary, Coin, CosmosMsg, WasmMsg}; /// Top level config type for core module. #[cw_serde] @@ -60,6 +60,8 @@ pub struct ModuleInstantiateInfo { /// CosmWasm level admin of the instantiated contract. See: /// pub admin: Option, + /// Funds to be sent to the instantiated contract. + pub funds: Vec, /// Label for the instantiated contract. pub label: String, } @@ -73,7 +75,7 @@ impl ModuleInstantiateInfo { }), code_id: self.code_id, msg: self.msg, - funds: vec![], + funds: self.funds, label: self.label, } } @@ -98,6 +100,7 @@ mod tests { msg: to_binary("foo").unwrap(), admin: None, label: "bar".to_string(), + funds: vec![], }; assert_eq!( no_admin.into_wasm_msg(Addr::unchecked("ekez")), @@ -120,6 +123,7 @@ mod tests { addr: "core".to_string(), }), label: "bar".to_string(), + funds: vec![], }; assert_eq!( no_admin.into_wasm_msg(Addr::unchecked("ekez")), @@ -140,6 +144,7 @@ mod tests { msg: to_binary("foo").unwrap(), admin: Some(Admin::CoreModule {}), label: "bar".to_string(), + funds: vec![], }; assert_eq!( no_admin.into_wasm_msg(Addr::unchecked("ekez")), diff --git a/packages/dao-testing/src/helpers.rs b/packages/dao-testing/src/helpers.rs index ed95ea770..9bccd53c7 100644 --- a/packages/dao-testing/src/helpers.rs +++ b/packages/dao-testing/src/helpers.rs @@ -70,12 +70,14 @@ pub fn instantiate_with_cw20_balances_governance( }) .unwrap(), admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { code_id: governance_code_id, msg: governance_instantiate, admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO governance module".to_string(), }], initial_items: None, @@ -153,6 +155,7 @@ pub fn instantiate_with_staked_balances_governance( }) .unwrap(), admin: None, + funds: vec![], label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { @@ -160,6 +163,7 @@ pub fn instantiate_with_staked_balances_governance( label: "DAO DAO governance module.".to_string(), admin: Some(Admin::CoreModule {}), msg: governance_instantiate, + funds: vec![], }], initial_items: None, }; @@ -272,12 +276,14 @@ pub fn instantiate_with_staking_active_threshold( }) .unwrap(), admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { code_id, msg: governance_instantiate, admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO governance module".to_string(), }], initial_items: None, @@ -344,12 +350,14 @@ pub fn instantiate_with_cw4_groups_governance( }) .unwrap(), admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { code_id: core_code_id, msg: proposal_module_instantiate, admin: Some(Admin::CoreModule {}), + funds: vec![], label: "DAO DAO governance module".to_string(), }], initial_items: None, diff --git a/test-contracts/dao-test-custom-factory/.cargo/config b/test-contracts/dao-test-custom-factory/.cargo/config new file mode 100644 index 000000000..336b618a1 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/test-contracts/dao-test-custom-factory/.gitignore b/test-contracts/dao-test-custom-factory/.gitignore new file mode 100644 index 000000000..dfdaaa6bc --- /dev/null +++ b/test-contracts/dao-test-custom-factory/.gitignore @@ -0,0 +1,15 @@ +# Build results +/target + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/test-contracts/dao-test-custom-factory/Cargo.toml b/test-contracts/dao-test-custom-factory/Cargo.toml new file mode 100644 index 000000000..ebd21e686 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "dao-test-custom-factory" +authors = ["ekez "] +description = "A proposal module that allows direct execution without voting." +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +dao-dao-macros = { workspace = true } +dao-interface = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } diff --git a/test-contracts/dao-test-custom-factory/README b/test-contracts/dao-test-custom-factory/README new file mode 100644 index 000000000..1be9ff991 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/README @@ -0,0 +1,2 @@ +# Test Custom Factory contract +Used for testing custom factories with `dao-voting-token-staked` and `dao-voting-cw721-staked`. diff --git a/test-contracts/dao-test-custom-factory/examples/schema.rs b/test-contracts/dao-test-custom-factory/examples/schema.rs new file mode 100644 index 000000000..f0a2ba4f4 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/examples/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use dao_test_custom_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + } +} diff --git a/test-contracts/dao-test-custom-factory/schema/admin_response.json b/test-contracts/dao-test-custom-factory/schema/admin_response.json new file mode 100644 index 000000000..920968701 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/admin_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "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" +} diff --git a/test-contracts/dao-test-custom-factory/schema/dao_response.json b/test-contracts/dao-test-custom-factory/schema/dao_response.json new file mode 100644 index 000000000..9518ba3b5 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "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" +} diff --git a/test-contracts/dao-test-custom-factory/schema/execute_msg.json b/test-contracts/dao-test-custom-factory/schema/execute_msg.json new file mode 100644 index 000000000..302a22adc --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/execute_msg.json @@ -0,0 +1,487 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "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", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "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" + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "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" + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/test-contracts/dao-test-custom-factory/schema/info_response.json b/test-contracts/dao-test-custom-factory/schema/info_response.json new file mode 100644 index 000000000..a0516764e --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/test-contracts/dao-test-custom-factory/schema/instantiate_msg.json b/test-contracts/dao-test-custom-factory/schema/instantiate_msg.json new file mode 100644 index 000000000..8e3416dc4 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/instantiate_msg.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "root" + ], + "properties": { + "root": { + "type": "string" + } + } +} diff --git a/test-contracts/dao-test-custom-factory/schema/query_msg.json b/test-contracts/dao-test-custom-factory/schema/query_msg.json new file mode 100644 index 000000000..5262b4290 --- /dev/null +++ b/test-contracts/dao-test-custom-factory/schema/query_msg.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/test-contracts/dao-test-custom-factory/src/contract.rs b/test-contracts/dao-test-custom-factory/src/contract.rs new file mode 100644 index 000000000..56a1a275e --- /dev/null +++ b/test-contracts/dao-test-custom-factory/src/contract.rs @@ -0,0 +1,91 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, + WasmMsg, +}; +use cw2::set_contract_version; + +use crate::{ + error::ContractError, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, +}; + +const CONTRACT_NAME: &str = "CARGO_PKG_NAME"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const INSTANTIATE_BASE_MINTER_REPLY_ID: u64 = 1; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::new().add_attribute("method", "instantiate")) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::StargazeBaseMinterFactory(msg) => { + execute_stargaze_base_minter_factory(deps, env, info, msg) + } + } +} + +pub fn execute_token_factory_factory( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: WasmMsg, +) -> Result { + unimplemented!() +} + +pub fn execute_stargaze_base_minter_factory( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: WasmMsg, +) -> Result { + // TODO query voting contract (the sender) for the DAO address + // TODO replace the Stargaze info to set the DAO address + + // TODO call base-factory to create minter + + // TODO this create a base-minter contract and a sg721 contract + + // in submsg reply, parse the response and save the contract address + unimplemented!() +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Info {} => query_info(deps), + } +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&dao_interface::voting::InfoResponse { info }) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match msg.id { + INSTANTIATE_BASE_MINTER_REPLY_ID => { + unimplemented!() + } + _ => Err(ContractError::UnknownReplyId { id: msg.id }), + } +} diff --git a/test-contracts/dao-test-custom-factory/src/error.rs b/test-contracts/dao-test-custom-factory/src/error.rs new file mode 100644 index 000000000..ee02b079a --- /dev/null +++ b/test-contracts/dao-test-custom-factory/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Got a submessage reply with unknown id: {id}")] + UnknownReplyId { id: u64 }, +} diff --git a/test-contracts/dao-test-custom-factory/src/lib.rs b/test-contracts/dao-test-custom-factory/src/lib.rs new file mode 100644 index 000000000..3915b791e --- /dev/null +++ b/test-contracts/dao-test-custom-factory/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +mod error; +pub mod msg; + +pub use crate::error::ContractError; diff --git a/test-contracts/dao-test-custom-factory/src/msg.rs b/test-contracts/dao-test-custom-factory/src/msg.rs new file mode 100644 index 000000000..1af086b5b --- /dev/null +++ b/test-contracts/dao-test-custom-factory/src/msg.rs @@ -0,0 +1,17 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::WasmMsg; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + StargazeBaseMinterFactory(WasmMsg), +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(dao_interface::voting::InfoResponse)] + Info {}, +}