From 69312b1c1d33e1aa6dd001285012dbb2a065fbfc Mon Sep 17 00:00:00 2001 From: Abhishek-1857 Date: Tue, 9 Apr 2024 16:03:06 +0530 Subject: [PATCH] proposal single testcases implemented --- Cargo.lock | 35 + Cargo.toml | 2 +- .../src/contract.rs | 13 +- .../dao-pre-propose-multiple/src/contract.rs | 4 +- .../dao-pre-propose-single/src/contract.rs | 4 +- .../proposal/dao-proposal-single/Cargo.toml | 12 +- .../dao-proposal-single/src/contract.rs | 230 +- .../proposal/dao-proposal-single/src/lib.rs | 4 +- .../proposal/dao-proposal-single/src/msg.rs | 6 +- .../proposal/dao-proposal-single/src/state.rs | 3 +- .../src/testing/adversarial_tests.rs | 770 +-- .../src/testing/contracts.rs | 53 +- .../src/testing/do_votes.rs | 809 ++-- .../src/testing/execute.rs | 510 +- .../src/testing/instantiate.rs | 503 +- .../src/testing/migration_tests.rs | 901 ++-- .../dao-proposal-single/src/testing/mod.rs | 1 + .../src/testing/queries.rs | 212 +- .../dao-proposal-single/src/testing/tests.rs | 4278 ++--------------- .../dao-proposal-single/src/v1_state.rs | 307 +- .../voting/dao-voting-cw4/src/contract.rs | 3 +- contracts/voting/dao-voting-cw4/src/msg.rs | 2 +- contracts/voting/dao-voting-cw4/src/tests.rs | 28 +- .../dao-voting-snip20-staked/src/contract.rs | 6 +- .../dao-voting-snip20-staked/src/msg.rs | 3 +- .../dao-voting-snip20-staked/src/tests.rs | 72 +- packages/dao-pre-propose-base/src/execute.rs | 13 +- packages/dao-pre-propose-base/src/msg.rs | 6 +- packages/dao-testing/Cargo.toml | 48 + packages/dao-testing/README.md | 4 + packages/dao-testing/src/contracts.rs | 167 + packages/dao-testing/src/lib.rs | 7 + packages/dao-testing/src/tests.rs | 635 +++ 33 files changed, 3611 insertions(+), 6040 deletions(-) create mode 100644 packages/dao-testing/Cargo.toml create mode 100644 packages/dao-testing/README.md create mode 100644 packages/dao-testing/src/contracts.rs create mode 100644 packages/dao-testing/src/lib.rs create mode 100644 packages/dao-testing/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 0a329fe..13044c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1152,11 +1152,13 @@ dependencies = [ "dao-interface", "dao-pre-propose-base", "dao-pre-propose-single", + "dao-testing", "dao-voting 2.4.0", "dao-voting-cw4", "dao-voting-snip20-staked", "dao-voting-snip721-staked", "dao-voting-token-staked", + "query_auth 0.1.0", "schemars 0.8.16", "secret-cosmwasm-std", "secret-cosmwasm-storage", @@ -1170,6 +1172,7 @@ dependencies = [ "shade-protocol", "snip20-reference-impl", "snip20-stake", + "snip721-reference-impl", "thiserror", ] @@ -1186,6 +1189,38 @@ dependencies = [ "shade-protocol", ] +[[package]] +name = "dao-testing" +version = "2.4.0" +dependencies = [ + "cosmwasm-schema 1.1.11", + "cw-hooks", + "cw-vesting", + "cw4", + "cw4-group", + "dao-dao-core", + "dao-interface", + "dao-voting 2.4.0", + "dao-voting-cw4", + "dao-voting-snip20-staked", + "dao-voting-snip721-roles", + "dao-voting-snip721-staked", + "dao-voting-token-staked", + "query_auth 0.1.0", + "rand", + "secret-cosmwasm-std", + "secret-cw2", + "secret-multi-test", + "secret-utils 0.13.4", + "serde", + "serde_json", + "shade-protocol", + "snip20-reference-impl", + "snip20-stake", + "snip721-reference-impl", + "snip721-roles", +] + [[package]] name = "dao-voting" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5c17a39..947d4ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.4.0" dao-voting-snip721-roles = { path = "./contracts/voting/dao-voting-snip721-roles", version = "2.4.0" ,default-features = false} dao-voting-snip721-staked = { path = "./contracts/voting/dao-voting-snip721-staked", version = "2.4.0",default-features = false } dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.4.0",default-features = false } - +dao-testing ={ path = "./packages/dao-testing/"} # v1 dependencies. used for state migrations. cw-core-v1 = { package = "cw-core", version = "0.1.0" ,default-features = false} cw-proposal-single-v1 = { package = "cw-proposal-single", version = "0.1.0",default-features = false } diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs b/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs index 7e35f25..95b03cf 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-approval-single/src/contract.rs @@ -49,7 +49,7 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Propose { msg, key } => execute_propose(deps, env, info, msg, key), + ExecuteMsg::Propose { msg, auth } => execute_propose(deps, env, info, msg, auth), ExecuteMsg::AddProposalSubmittedHook { address, code_hash } => { execute_add_approver_hook(deps, info, address, code_hash) @@ -73,17 +73,12 @@ pub fn execute_propose( env: Env, info: MessageInfo, msg: ProposeMessage, - key: String, + auth: Auth, ) -> Result { let pre_propose_base = PrePropose::default(); let config = pre_propose_base.config.load(deps.storage)?; - let auth = Auth::ViewingKey { - key: key.clone(), - address: info.sender.clone().to_string(), - }; - - pre_propose_base.check_can_submit(deps.as_ref(), auth)?; + pre_propose_base.check_can_submit(deps.as_ref(), auth.clone())?; // Take deposit, if configured. let deposit_messages = if let Some(ref deposit_info) = config.deposit_info { @@ -123,7 +118,7 @@ pub fn execute_propose( description: propose_msg_internal.description.clone(), approval_id, }, - key: key.clone(), + auth: auth.clone(), })?, funds: vec![], }; diff --git a/contracts/pre-propose/dao-pre-propose-multiple/src/contract.rs b/contracts/pre-propose/dao-pre-propose-multiple/src/contract.rs index 651138e..2f4529b 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/contract.rs @@ -74,7 +74,7 @@ pub fn execute( description, choices, }, - key, + auth, } => ExecuteInternal::Propose { msg: ProposeMessageInternal::Propose { proposer: Some(info.sender.to_string()), @@ -82,7 +82,7 @@ pub fn execute( description, choices, }, - key, + auth, }, ExecuteMsg::Extension { msg } => ExecuteInternal::Extension { msg }, ExecuteMsg::Withdraw { denom, key } => ExecuteInternal::Withdraw { denom, key }, diff --git a/contracts/pre-propose/dao-pre-propose-single/src/contract.rs b/contracts/pre-propose/dao-pre-propose-single/src/contract.rs index abd5370..0404ca1 100644 --- a/contracts/pre-propose/dao-pre-propose-single/src/contract.rs +++ b/contracts/pre-propose/dao-pre-propose-single/src/contract.rs @@ -75,7 +75,7 @@ pub fn execute( description, msgs, }, - key, + auth, } => ExecuteInternal::Propose { msg: ProposeMessageInternal::Propose(ProposeMsg { proposer: Some(info.sender.to_string()), @@ -83,7 +83,7 @@ pub fn execute( description, msgs, }), - key, + auth, }, ExecuteMsg::Extension { msg } => ExecuteInternal::Extension { msg }, ExecuteMsg::Withdraw { denom, key } => ExecuteInternal::Withdraw { denom, key }, diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index d20365f..e2c44f7 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -36,10 +36,11 @@ dao-hooks = { workspace = true } serde ={ workspace=true } schemars ={ workspace = true } secret-cw-controllers ={ workspace = true } -# cw-utils-v1 = { workspace = true} -# voting-v1 = { workspace = true } -# cw-proposal-single-v1 = { workspace = true, features = ["library"] } shade-protocol ={ workspace = true } +query_auth ={ workspace = true } +dao-pre-propose-single = { workspace = true } +dao-testing ={ workspace = true } + [dev-dependencies] anyhow = { workspace = true } @@ -47,16 +48,13 @@ cosmwasm-schema = { workspace = true } secret-multi-test = { workspace = true } dao-dao-core = { workspace = true } dao-voting-cw4 = { workspace = true } -# dao-voting-snip20-balance = { workspace = true } dao-voting-snip20-staked = { workspace = true } dao-voting-token-staked = { workspace = true } dao-voting-snip721-staked = { workspace = true } dao-pre-propose-single = { workspace = true } cw-denom = { workspace = true } -# dao-testing = { workspace = true } snip20-stake = { workspace = true } snip20-reference-impl = { workspace = true } -# cw721-base = { workspace = true } +snip721-reference-impl = { workspace = true } cw4 = { workspace = true } cw4-group = { workspace = true } -# cw-core-v1 = { workspace = true, features = ["library"] } diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index 7c18769..70072ea 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -1,5 +1,6 @@ use std::borrow::Borrow; +use crate::msg::MigrateMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -21,7 +22,7 @@ use dao_voting::status::Status; use dao_voting::threshold::Threshold; use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::voting::{get_total_power, get_voting_power, validate_voting_period, Vote, Votes}; -use secret_cw2::set_contract_version; +use secret_cw2::{get_contract_version, set_contract_version, ContractVersion}; use secret_cw_controllers::ReplyEvent; use secret_toolkit::utils::HandleCallback; use secret_utils::{parse_reply_event_for_contract_address, Duration}; @@ -34,9 +35,9 @@ use shade_protocol::Contract; // use crate::msg::MigrateMsg; use crate::proposal::{next_proposal_id, SingleChoiceProposal}; use crate::state::{Ballot, Config, CREATION_POLICY, DAO, REPLY_IDS}; -// use crate::v1_state::{ -// v1_duration_to_v2, v1_expiration_to_v2, v1_status_to_v2, v1_threshold_to_v2, v1_votes_to_v2, -// }; +use crate::v1_state::{ + v1_duration_to_v2, v1_expiration_to_v2, v1_status_to_v2, v1_threshold_to_v2, v1_votes_to_v2, +}; use crate::{ error::ContractError, msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, @@ -120,17 +121,17 @@ pub fn execute( proposer, }) => execute_propose(deps, env, info.sender, title, description, msgs, proposer), ExecuteMsg::Vote { - key, + auth, proposal_id, vote, rationale, - } => execute_vote(deps, env, info, key, proposal_id, vote, rationale), + } => execute_vote(deps, env, info, auth, proposal_id, vote, rationale), ExecuteMsg::UpdateRationale { proposal_id, rationale, } => execute_update_rationale(deps, info, proposal_id, rationale), - ExecuteMsg::Execute { key, proposal_id } => { - execute_execute(deps, env, info, key, proposal_id) + ExecuteMsg::Execute { auth, proposal_id } => { + execute_execute(deps, env, info, auth, proposal_id) } ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), ExecuteMsg::UpdateConfig { @@ -373,7 +374,7 @@ pub fn execute_execute( deps: DepsMut, env: Env, info: MessageInfo, - key: String, + auth: Auth, proposal_id: u64, ) -> Result { let dao_info = DAO.load(deps.storage)?; @@ -382,10 +383,6 @@ pub fn execute_execute( .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; let config = CONFIG.load(deps.storage)?; - let auth = Auth::ViewingKey { - key, - address: info.sender.clone().to_string(), - }; // determine if this sender can execute let mut sender_can_execute = true; @@ -502,7 +499,7 @@ pub fn execute_vote( deps: DepsMut, env: Env, info: MessageInfo, - key: String, + auth: Auth, proposal_id: u64, vote: Vote, rationale: Option, @@ -524,10 +521,6 @@ pub fn execute_vote( return Err(ContractError::Expired { id: proposal_id }); } - let auth = Auth::ViewingKey { - key, - address: info.sender.clone().to_string(), - }; let vote_power = get_voting_power( deps.as_ref(), dao_info.code_hash.clone(), @@ -1113,108 +1106,105 @@ pub fn query_info(deps: Deps) -> StdResult { to_binary(&dao_interface::voting::InfoResponse { info }) } -// // #[cfg_attr(not(feature = "library"), entry_point)] -// // pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { -// // let ContractVersion { version, .. } = get_contract_version(deps.storage)?; -// // set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - -// // match msg { -// // MigrateMsg::FromV1 { -// // close_proposal_on_execution_failure, -// // pre_propose_info, -// // veto, -// // } => { -// // // `CONTRACT_VERSION` here is from the data section of the -// // // blob we are migrating to. `version` is from storage. If -// // // the version in storage matches the version in the blob -// // // we are not upgrading. -// // if version == CONTRACT_VERSION { -// // return Err(ContractError::AlreadyMigrated {}); -// // } - -// // let current_config = v1::state::CONFIG.load(deps.storage)?; -// // let max_voting_period = v1_duration_to_v2(current_config.max_voting_period); - -// // // if veto is configured, validate its fields -// // if let Some(veto_config) = &veto { -// // veto_config.validate(&deps.as_ref(), &max_voting_period)?; -// // }; - -// // // Update the stored config to have the new -// // // `close_proposal_on_execution_failure` field. -// // CONFIG.save( -// // deps.storage, -// // &Config { -// // threshold: v1_threshold_to_v2(current_config.threshold), -// // max_voting_period, -// // min_voting_period: current_config.min_voting_period.map(v1_duration_to_v2), -// // only_members_execute: current_config.only_members_execute, -// // allow_revoting: current_config.allow_revoting, -// // dao: current_config.dao.clone(), -// // close_proposal_on_execution_failure, -// // veto, -// // }, -// // )?; - -// // let (initial_policy, pre_propose_messages) = -// // pre_propose_info.into_initial_policy_and_messages(current_config.dao)?; -// // CREATION_POLICY.save(deps.storage, &initial_policy)?; - -// // // Update the module's proposals to v2. - -// // let current_proposals = v1::state::PROPOSALS -// // .range(deps.storage, None, None, Order::Ascending) -// // .collect::>>()?; - -// // // Based on gas usage testing, we estimate that we will be -// // // able to migrate ~4200 proposals at a time before -// // // reaching the block max_gas limit. -// // current_proposals -// // .into_iter() -// // .try_for_each::<_, Result<_, ContractError>>(|(id, prop)| { -// // if prop -// // .deposit_info -// // .map(|info| !info.deposit.is_zero()) -// // .unwrap_or(false) -// // && prop.status != voting_v1::Status::Closed -// // && prop.status != voting_v1::Status::Executed -// // { -// // // No migration path for outstanding -// // // deposits. -// // return Err(ContractError::PendingProposals {}); -// // } - -// // let migrated_proposal = SingleChoiceProposal { -// // title: prop.title, -// // description: prop.description, -// // proposer: prop.proposer, -// // start_height: prop.start_height, -// // min_voting_period: prop.min_voting_period.map(v1_expiration_to_v2), -// // expiration: v1_expiration_to_v2(prop.expiration), -// // threshold: v1_threshold_to_v2(prop.threshold), -// // total_power: prop.total_power, -// // msgs: prop.msgs, -// // status: v1_status_to_v2(prop.status), -// // votes: v1_votes_to_v2(prop.votes), -// // allow_revoting: prop.allow_revoting, -// // veto: None, -// // }; - -// // PROPOSALS -// // .save(deps.storage, id, &migrated_proposal) -// // .map_err(|e| e.into()) -// // })?; - -// // Ok(Response::default() -// // .add_attribute("action", "migrate") -// // .add_attribute("from", "v1") -// // .add_submessages(pre_propose_messages)) -// // } -// // MigrateMsg::FromCompatible {} => Ok(Response::default() -// // .add_attribute("action", "migrate") -// // .add_attribute("from", "compatible")), -// // } -// // } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + let ContractVersion { version, .. } = get_contract_version(deps.storage)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + match msg { + MigrateMsg::FromV1 { + close_proposal_on_execution_failure, + pre_propose_info, + veto, + } => { + // `CONTRACT_VERSION` here is from the data section of the + // blob we are migrating to. `version` is from storage. If + // the version in storage matches the version in the blob + // we are not upgrading. + if version == CONTRACT_VERSION { + return Err(ContractError::AlreadyMigrated {}); + } + + let current_config = crate::state::CONFIG.load(deps.storage)?; + let max_voting_period = v1_duration_to_v2(current_config.max_voting_period); + let dao = DAO.load(deps.storage)?.addr; + + // if veto is configured, validate its fields + if let Some(veto_config) = &veto { + veto_config.validate(&deps.as_ref(), &max_voting_period)?; + }; + + // Update the stored config to have the new + // `close_proposal_on_execution_failure` field. + CONFIG.save( + deps.storage, + &Config { + threshold: v1_threshold_to_v2(current_config.threshold), + max_voting_period, + min_voting_period: current_config.min_voting_period.map(v1_duration_to_v2), + only_members_execute: current_config.only_members_execute, + allow_revoting: current_config.allow_revoting, + close_proposal_on_execution_failure, + veto, + query_auth: current_config.query_auth, + }, + )?; + + let (initial_policy, pre_propose_messages) = + pre_propose_info.into_initial_policy_and_messages(deps.storage, dao, &REPLY_IDS)?; + CREATION_POLICY.save(deps.storage, &initial_policy)?; + + // Update the module's proposals to v2. + + let current_proposals = crate::state::PROPOSALS + .iter(deps.storage)? + .collect::>>()?; + + // Based on gas usage testing, we estimate that we will be + // able to migrate ~4200 proposals at a time before + // reaching the block max_gas limit. + current_proposals + .into_iter() + .try_for_each::<_, Result<_, ContractError>>(|(id, prop)| { + if prop.status != dao_voting::status::Status::Closed + && prop.status != dao_voting::status::Status::Executed + { + // No migration path for outstanding + // deposits. + return Err(ContractError::PendingProposals {}); + } + + let migrated_proposal = SingleChoiceProposal { + title: prop.title, + description: prop.description, + proposer: prop.proposer, + start_height: prop.start_height, + min_voting_period: prop.min_voting_period.map(v1_expiration_to_v2), + expiration: v1_expiration_to_v2(prop.expiration), + threshold: v1_threshold_to_v2(prop.threshold), + total_power: prop.total_power, + msgs: prop.msgs, + status: v1_status_to_v2(prop.status), + votes: v1_votes_to_v2(prop.votes), + allow_revoting: prop.allow_revoting, + veto: None, + }; + + PROPOSALS + .insert(deps.storage, &id, &migrated_proposal) + .map_err(|e| e.into()) + })?; + + Ok(Response::default() + .add_attribute("action", "migrate") + .add_attribute("from", "v1") + .add_submessages(pre_propose_messages)) + } + MigrateMsg::FromCompatible {} => Ok(Response::default() + .add_attribute("action", "migrate") + .add_attribute("from", "compatible")), + } +} #[cfg_attr(not(feature = "library"), entry_point)] pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { diff --git a/contracts/proposal/dao-proposal-single/src/lib.rs b/contracts/proposal/dao-proposal-single/src/lib.rs index fb14bf9..b556f8b 100644 --- a/contracts/proposal/dao-proposal-single/src/lib.rs +++ b/contracts/proposal/dao-proposal-single/src/lib.rs @@ -6,8 +6,8 @@ pub mod msg; pub mod proposal; pub mod query; -// #[cfg(test)] -// mod testing; +#[cfg(test)] +mod testing; pub mod state; pub mod v1_state; diff --git a/contracts/proposal/dao-proposal-single/src/msg.rs b/contracts/proposal/dao-proposal-single/src/msg.rs index c0717c2..7d818ac 100644 --- a/contracts/proposal/dao-proposal-single/src/msg.rs +++ b/contracts/proposal/dao-proposal-single/src/msg.rs @@ -62,8 +62,7 @@ pub enum ExecuteMsg { /// Votes on a proposal. Voting power is determined by the DAO's /// voting power module. Vote { - /// The viewing key of the sender - key: String, + auth: Auth, /// The ID of the proposal to vote on. proposal_id: u64, /// The senders position on the proposal. @@ -82,8 +81,7 @@ pub enum ExecuteMsg { /// Causes the messages associated with a passed proposal to be /// executed by the DAO. Execute { - /// The viewing of the sender - key: String, + auth: Auth, /// The ID of the proposal to execute. proposal_id: u64, }, diff --git a/contracts/proposal/dao-proposal-single/src/state.rs b/contracts/proposal/dao-proposal-single/src/state.rs index be05b40..eb5924e 100644 --- a/contracts/proposal/dao-proposal-single/src/state.rs +++ b/contracts/proposal/dao-proposal-single/src/state.rs @@ -6,7 +6,7 @@ use dao_voting::{ }; use schemars::JsonSchema; use secret_cw_controllers::ReplyIds; -use secret_storage_plus::Item; +use secret_storage_plus::{Item, Map}; use secret_toolkit::{serialization::Json, storage::Keymap}; use secret_utils::Duration; use serde::{Deserialize, Serialize}; @@ -85,3 +85,4 @@ pub const VOTE_HOOKS: Hooks = Hooks::new("vote_hooks"); pub const CREATION_POLICY: Item = Item::new("creation_policy"); pub const DAO: Item = Item::new("dao"); pub static REPLY_IDS: ReplyIds = ReplyIds::new(b"reply_ids", b"reply_ids_count"); +pub const DUMMY: Map = Map::new("d"); diff --git a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs index b8883e9..0b69da2 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -1,383 +1,387 @@ -use crate::msg::InstantiateMsg; -use crate::testing::instantiate::get_pre_propose_info; -use crate::testing::{ - execute::{ - close_proposal, execute_proposal, execute_proposal_should_fail, make_proposal, mint_cw20s, - vote_on_proposal, - }, - instantiate::{ - get_default_token_dao_proposal_module_instantiate, - instantiate_with_staked_balances_governance, - }, - queries::{query_balance_cw20, query_dao_token, query_proposal, query_single_proposal_module}, -}; -use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, Decimal, Uint128, WasmMsg}; -use cw20::Cw20Coin; -use cw_multi_test::{next_block, App}; -use cw_utils::Duration; -use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, - status::Status, - threshold::{PercentageThreshold, Threshold::AbsolutePercentage}, - voting::Vote, -}; - -use super::CREATOR_ADDR; -use crate::{query::ProposalResponse, ContractError}; - -struct CommonTest { - app: App, - proposal_module: Addr, - proposal_id: u64, -} -fn setup_test(messages: Vec) -> CommonTest { - let mut app = App::default(); - let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - // Mint some tokens to pay the proposal deposit. - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages); - - CommonTest { - app, - proposal_module, - proposal_id, - } -} - -// A proposal that is still accepting votes (is open) cannot -// be executed. Any attempts to do so should fail and return -// an error. -#[test] -fn test_execute_proposal_open() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - app.update_block(next_block); - - // assert proposal is open - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Open); - - // attempt to execute and assert that it fails - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})) -} - -// A proposal can be executed if and only if it passed. -// Any attempts to execute a proposal that has been rejected -// or closed (after rejection) should fail and return an error. -#[test] -fn test_execute_proposal_rejected_closed() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - // Assert proposal is open and vote enough to reject it - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Open); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - ); - - app.update_block(next_block); - - // Assert proposal is rejected - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Rejected); - - // Attempt to execute - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})); - - app.update_block(next_block); - - // close the proposal - close_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Closed); - - // Attempt to execute - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})) -} - -// A proposal can only be executed once. Any subsequent -// attempts to execute it should fail and return an error. -#[test] -fn test_execute_proposal_more_than_once() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - // Assert proposal is open and vote enough to reject it - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Open); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - - app.update_block(next_block); - - // assert proposal is passed, execute it - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - app.update_block(next_block); - - // assert proposal executed and attempt to execute it again - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - let err: ContractError = - execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})); -} - -// After proposal is executed, no subsequent votes -// should change the status of the proposal, even if -// the votes should shift to the opposing direction. -#[test] -pub fn test_executed_prop_state_remains_after_vote_swing() { - let mut app = App::default(); - - let instantiate = InstantiateMsg { - veto: None, - threshold: AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(15)), - }, - max_voting_period: Duration::Time(604800), // One week. - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(10_000_000), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - close_proposal_on_execution_failure: true, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "threshold".to_string(), - amount: Uint128::new(20), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(50), - }, - Cw20Coin { - address: "overslept_vote".to_string(), - amount: Uint128::new(30), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - // someone quickly votes, proposal gets executed - vote_on_proposal( - &mut app, - &proposal_module, - "threshold", - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - app.update_block(next_block); - - // assert prop is executed prior to its expiry - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); - assert!(!proposal.proposal.expiration.is_expired(&app.block_info())); - - // someone wakes up and casts their vote to express their - // opinion (not affecting the result of proposal) - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - ); - vote_on_proposal( - &mut app, - &proposal_module, - "overslept_vote", - proposal_id, - Vote::No, - ); - - app.update_block(next_block); - - // assert that everyone's votes are reflected in the proposal - // and proposal remains in executed state - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); - assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); -} - -// After reaching a passing state, no subsequent votes -// should change the status of the proposal, even if -// the votes should shift to the opposing direction. -#[test] -pub fn test_passed_prop_state_remains_after_vote_swing() { - let mut app = App::default(); - - let instantiate = InstantiateMsg { - veto: None, - threshold: AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(15)), - }, - max_voting_period: Duration::Time(604800), // One week. - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(10_000_000), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - close_proposal_on_execution_failure: true, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "threshold".to_string(), - amount: Uint128::new(20), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(50), - }, - Cw20Coin { - address: "overslept_vote".to_string(), - amount: Uint128::new(30), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - // if the proposal passes, it should mint 100_000_000 tokens to "threshold" - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: "threshold".to_string(), - amount: Uint128::new(100_000_000), - }; - let binary_msg = to_json_binary(&msg).unwrap(); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: binary_msg, - funds: vec![], - } - .into()], - ); - - // assert that the initial "threshold" address balance is 0 - let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); - assert_eq!(balance, Uint128::zero()); - - // vote enough to pass the proposal - vote_on_proposal( - &mut app, - &proposal_module, - "threshold", - proposal_id, - Vote::Yes, - ); - - // assert proposal is passed with 20 votes in favor and none opposed - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); - assert_eq!(proposal.proposal.votes.no, Uint128::zero()); - - app.update_block(next_block); - - // the other voters wake up, vote against the proposal - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - ); - vote_on_proposal( - &mut app, - &proposal_module, - "overslept_vote", - proposal_id, - Vote::No, - ); - - app.update_block(next_block); - - // assert that the late votes have been counted and proposal - // is still in passed state before executing it - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); - assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); - - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - app.update_block(next_block); - - // make sure that the initial "threshold" address balance is - // 100_000_000 and late votes did not make a difference - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); - assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); - let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); - assert_eq!(balance, Uint128::new(100_000_000)); -} +// use crate::msg::InstantiateMsg; +// use crate::testing::instantiate::get_pre_propose_info; +// use crate::testing::{ +// execute::{ +// close_proposal, execute_proposal, execute_proposal_should_fail, make_proposal, mint_cw20s, +// vote_on_proposal, +// }, +// instantiate::{ +// get_default_token_dao_proposal_module_instantiate, +// instantiate_with_staked_balances_governance, +// }, +// queries::{query_balance_cw20, query_dao_token, query_proposal, query_single_proposal_module}, +// }; +// use cosmwasm_std::{to_binary, Addr, ContractInfo, CosmosMsg, Decimal, Uint128, WasmMsg}; +// use dao_voting::{ +// deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, +// status::Status, +// threshold::{PercentageThreshold, Threshold::AbsolutePercentage}, +// voting::Vote, +// }; +// use secret_multi_test::{next_block, App}; +// use secret_utils::Duration; +// use snip20_reference_impl::msg::InitialBalance; + +// use super::CREATOR_ADDR; +// use crate::{query::ProposalResponse, ContractError}; + +// struct CommonTest { +// app: App, +// proposal_module: ContractInfo, +// proposal_id: u64, +// } +// fn setup_test(messages: Vec) -> CommonTest { +// let mut app = App::default(); +// let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); +// let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); +// let proposal_module = query_single_proposal_module(&app, &core_addr); +// let gov_token = query_dao_token(&app, &core_addr); + +// // Mint some tokens to pay the proposal deposit. +// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); +// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages); + +// CommonTest { +// app, +// proposal_module, +// proposal_id, +// } +// } + +// // A proposal that is still accepting votes (is open) cannot +// // be executed. Any attempts to do so should fail and return +// // an error. +// #[test] +// fn test_execute_proposal_open() { +// let CommonTest { +// mut app, +// proposal_module, +// proposal_id, +// } = setup_test(vec![]); + +// app.update_block(next_block); + +// // assert proposal is open +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Open); + +// // attempt to execute and assert that it fails +// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// assert!(matches!(err, ContractError::NotPassed {})) +// } + +// // A proposal can be executed if and only if it passed. +// // Any attempts to execute a proposal that has been rejected +// // or closed (after rejection) should fail and return an error. +// #[test] +// fn test_execute_proposal_rejected_closed() { +// let CommonTest { +// mut app, +// proposal_module, +// proposal_id, +// } = setup_test(vec![]); + +// // Assert proposal is open and vote enough to reject it +// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); +// assert_eq!(proposal.proposal.status, Status::Open); +// vote_on_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// proposal_id, +// Vote::No, +// ); + +// app.update_block(next_block); + +// // Assert proposal is rejected +// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Rejected); + +// // Attempt to execute +// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// assert!(matches!(err, ContractError::NotPassed {})); + +// app.update_block(next_block); + +// // close the proposal +// close_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Closed); + +// // Attempt to execute +// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// assert!(matches!(err, ContractError::NotPassed {})) +// } + +// // A proposal can only be executed once. Any subsequent +// // attempts to execute it should fail and return an error. +// #[test] +// fn test_execute_proposal_more_than_once() { +// let CommonTest { +// mut app, +// proposal_module, +// proposal_id, +// } = setup_test(vec![]); + +// // Assert proposal is open and vote enough to reject it +// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Open); +// vote_on_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// proposal_id, +// Vote::Yes, +// ); + +// app.update_block(next_block); + +// // assert proposal is passed, execute it +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Passed); +// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + +// app.update_block(next_block); + +// // assert proposal executed and attempt to execute it again +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Executed); +// let err: ContractError = +// execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); +// assert!(matches!(err, ContractError::NotPassed {})); +// } + +// // After proposal is executed, no subsequent votes +// // should change the status of the proposal, even if +// // the votes should shift to the opposing direction. +// #[test] +// pub fn test_executed_prop_state_remains_after_vote_swing() { +// let mut app = App::default(); + +// let instantiate = InstantiateMsg { +// veto: None, +// threshold: AbsolutePercentage { +// percentage: PercentageThreshold::Percent(Decimal::percent(15)), +// }, +// max_voting_period: Duration::Time(604800), // One week. +// min_voting_period: None, +// only_members_execute: true, +// allow_revoting: false, +// pre_propose_info: get_pre_propose_info( +// &mut app, +// Some(UncheckedDepositInfo { +// denom: dao_voting::deposit::DepositToken::VotingModuleToken { +// token_type: VotingModuleTokenType::Cw20, +// }, +// amount: Uint128::new(10_000_000), +// refund_policy: DepositRefundPolicy::OnlyPassed, +// }), +// false, +// ), +// close_proposal_on_execution_failure: true, +// dao_code_hash: "dao_code_hash".to_string(), +// query_auth: todo!(), +// }; + +// let core_addr = instantiate_with_staked_balances_governance( +// &mut app, +// instantiate, +// Some(vec![ +// InitialBalance { +// address: "threshold".to_string(), +// amount: Uint128::new(20), +// }, +// InitialBalance { +// address: CREATOR_ADDR.to_string(), +// amount: Uint128::new(50), +// }, +// InitialBalance { +// address: "overslept_vote".to_string(), +// amount: Uint128::new(30), +// }, +// ]), +// ); +// let proposal_module = query_single_proposal_module(&app, &core_addr); +// let gov_token = query_dao_token(&app, &core_addr); + +// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); +// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + +// // someone quickly votes, proposal gets executed +// vote_on_proposal( +// &mut app, +// &proposal_module, +// "threshold", +// proposal_id, +// Vote::Yes, +// ); +// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + +// app.update_block(next_block); + +// // assert prop is executed prior to its expiry +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Executed); +// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); +// assert!(!proposal.proposal.expiration.is_expired(&app.block_info())); + +// // someone wakes up and casts their vote to express their +// // opinion (not affecting the result of proposal) +// vote_on_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// proposal_id, +// Vote::No, +// ); +// vote_on_proposal( +// &mut app, +// &proposal_module, +// "overslept_vote", +// proposal_id, +// Vote::No, +// ); + +// app.update_block(next_block); + +// // assert that everyone's votes are reflected in the proposal +// // and proposal remains in executed state +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Executed); +// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); +// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); +// } + +// // After reaching a passing state, no subsequent votes +// // should change the status of the proposal, even if +// // the votes should shift to the opposing direction. +// #[test] +// pub fn test_passed_prop_state_remains_after_vote_swing() { +// let mut app = App::default(); + +// let instantiate = InstantiateMsg { +// veto: None, +// threshold: AbsolutePercentage { +// percentage: PercentageThreshold::Percent(Decimal::percent(15)), +// }, +// max_voting_period: Duration::Time(604800), // One week. +// min_voting_period: None, +// only_members_execute: true, +// allow_revoting: false, +// pre_propose_info: get_pre_propose_info( +// &mut app, +// Some(UncheckedDepositInfo { +// denom: dao_voting::deposit::DepositToken::VotingModuleToken { +// token_type: VotingModuleTokenType::Cw20, +// }, +// amount: Uint128::new(10_000_000), +// refund_policy: DepositRefundPolicy::OnlyPassed, +// }), +// false, +// ), +// close_proposal_on_execution_failure: true, +// dao_code_hash: todo!(), +// query_auth: todo!(), +// }; + +// let core_addr = instantiate_with_staked_balances_governance( +// &mut app, +// instantiate, +// Some(vec![ +// Cw20Coin { +// address: "threshold".to_string(), +// amount: Uint128::new(20), +// }, +// Cw20Coin { +// address: CREATOR_ADDR.to_string(), +// amount: Uint128::new(50), +// }, +// Cw20Coin { +// address: "overslept_vote".to_string(), +// amount: Uint128::new(30), +// }, +// ]), +// ); +// let proposal_module = query_single_proposal_module(&app, &core_addr); +// let gov_token = query_dao_token(&app, &core_addr); + +// // if the proposal passes, it should mint 100_000_000 tokens to "threshold" +// let msg = cw20::Cw20ExecuteMsg::Mint { +// recipient: "threshold".to_string(), +// amount: Uint128::new(100_000_000), +// }; +// let binary_msg = to_binary(&msg).unwrap(); + +// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); +// let proposal_id = make_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// vec![WasmMsg::Execute { +// contract_addr: gov_token.to_string(), +// msg: binary_msg, +// funds: vec![], +// } +// .into()], +// ); + +// // assert that the initial "threshold" address balance is 0 +// let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); +// assert_eq!(balance, Uint128::zero()); + +// // vote enough to pass the proposal +// vote_on_proposal( +// &mut app, +// &proposal_module, +// "threshold", +// proposal_id, +// Vote::Yes, +// ); + +// // assert proposal is passed with 20 votes in favor and none opposed +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Passed); +// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); +// assert_eq!(proposal.proposal.votes.no, Uint128::zero()); + +// app.update_block(next_block); + +// // the other voters wake up, vote against the proposal +// vote_on_proposal( +// &mut app, +// &proposal_module, +// CREATOR_ADDR, +// proposal_id, +// Vote::No, +// ); +// vote_on_proposal( +// &mut app, +// &proposal_module, +// "overslept_vote", +// proposal_id, +// Vote::No, +// ); + +// app.update_block(next_block); + +// // assert that the late votes have been counted and proposal +// // is still in passed state before executing it +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Passed); +// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); +// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); + +// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); + +// app.update_block(next_block); + +// // make sure that the initial "threshold" address balance is +// // 100_000_000 and late votes did not make a difference +// let proposal = query_proposal(&app, &proposal_module, proposal_id); +// assert_eq!(proposal.proposal.status, Status::Executed); +// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); +// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); +// let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); +// assert_eq!(balance, Uint128::new(100_000_000)); +// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index d222acb..668fdd2 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -1,13 +1,13 @@ use cosmwasm_std::Empty; -use cw_multi_test::{Contract, ContractWrapper}; use dao_pre_propose_single as cppbps; +use secret_multi_test::{Contract, ContractWrapper}; -pub(crate) fn cw20_base_contract() -> Box> { +pub(crate) fn snip20_base_contract() -> Box> { let contract = ContractWrapper::new( - cw20_base::contract::execute, - cw20_base::contract::instantiate, - cw20_base::contract::query, + snip20_reference_impl::contract::execute, + snip20_reference_impl::contract::instantiate, + snip20_reference_impl::contract::query, ); Box::new(contract) } @@ -21,20 +21,20 @@ pub(crate) fn cw4_group_contract() -> Box> { Box::new(contract) } -pub(crate) fn cw721_base_contract() -> Box> { +pub(crate) fn snip721_base_contract() -> Box> { let contract = ContractWrapper::new( - cw721_base::entry::execute, - cw721_base::entry::instantiate, - cw721_base::entry::query, + snip721_reference_impl::contract::execute, + snip721_reference_impl::contract::instantiate, + snip721_reference_impl::contract::query, ); Box::new(contract) } -pub(crate) fn cw20_stake_contract() -> Box> { +pub(crate) fn snip20_stake_contract() -> Box> { let contract = ContractWrapper::new( - cw20_stake::contract::execute, - cw20_stake::contract::instantiate, - cw20_stake::contract::query, + snip20_stake::contract::execute, + snip20_stake::contract::instantiate, + snip20_stake::contract::query, ); Box::new(contract) } @@ -59,13 +59,13 @@ pub(crate) fn pre_propose_single_contract() -> Box> { Box::new(contract) } -pub(crate) fn cw20_staked_balances_voting_contract() -> Box> { +pub(crate) fn snip20_staked_balances_voting_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_cw20_staked::contract::execute, - dao_voting_cw20_staked::contract::instantiate, - dao_voting_cw20_staked::contract::query, + dao_voting_snip20_staked::contract::execute, + dao_voting_snip20_staked::contract::instantiate, + dao_voting_snip20_staked::contract::query, ) - .with_reply(dao_voting_cw20_staked::contract::reply); + .with_reply(dao_voting_snip20_staked::contract::reply); Box::new(contract) } @@ -78,11 +78,11 @@ pub(crate) fn native_staked_balances_voting_contract() -> Box Box> { +pub(crate) fn snip721_stake_contract() -> Box> { let contract = ContractWrapper::new( - dao_voting_cw721_staked::contract::execute, - dao_voting_cw721_staked::contract::instantiate, - dao_voting_cw721_staked::contract::query, + dao_voting_snip721_staked::contract::execute, + dao_voting_snip721_staked::contract::instantiate, + dao_voting_snip721_staked::contract::query, ); Box::new(contract) } @@ -106,3 +106,12 @@ pub(crate) fn cw4_voting_contract() -> Box> { .with_reply(dao_voting_cw4::contract::reply); Box::new(contract) } + +pub(crate) fn query_auth_contract() -> Box> { + let contract = ContractWrapper::new( + query_auth::contract::execute, + query_auth::contract::instantiate, + query_auth::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs index aad0cdd..ec8f97a 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs @@ -1,399 +1,410 @@ -use std::mem::discriminant; - -use cosmwasm_std::{coins, Addr, Coin, Uint128}; -use cw20::Cw20Coin; - -use cw_multi_test::{App, BankSudo, Executor, SudoMsg}; -use dao_interface::state::ProposalModule; -use dao_pre_propose_single as cppbps; - -use cw_denom::CheckedDenom; -use dao_testing::{ShouldExecute, TestSingleChoiceVote}; -use dao_voting::{ - deposit::{CheckedDepositInfo, UncheckedDepositInfo}, - status::Status, - threshold::Threshold, -}; - -use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - query::{ProposalResponse, VoteInfo, VoteResponse}, - testing::{instantiate::*, queries::query_deposit_config_and_pre_propose_module}, -}; - -pub(crate) fn do_votes_staked_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None::, - instantiate_with_staked_balances_governance, - ); -} - -pub(crate) fn do_votes_nft_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None, - instantiate_with_staked_cw721_governance, - ); -} - -pub(crate) fn do_votes_native_staked_balances( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None, - instantiate_with_native_staked_balances_governance, - ); -} - -pub(crate) fn do_votes_cw4_weights( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, -) { - do_test_votes( - votes, - threshold, - expected_status, - total_supply, - None::, - instantiate_with_cw4_groups_governance, - ); -} - -fn do_test_votes( - votes: Vec, - threshold: Threshold, - expected_status: Status, - total_supply: Option, - deposit_info: Option, - setup_governance: F, -) -> (App, Addr) -where - F: Fn(&mut App, InstantiateMsg, Option>) -> Addr, -{ - let mut app = App::default(); - - // Mint some ujuno so that it exists for native staking tests - // Otherwise denom validation will fail - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: "sodenomexists".to_string(), - amount: vec![Coin { - amount: Uint128::new(10), - denom: "ujuno".to_string(), - }], - })) - .unwrap(); - - let mut initial_balances = votes - .iter() - .map(|TestSingleChoiceVote { voter, weight, .. }| Cw20Coin { - address: voter.to_string(), - amount: *weight, - }) - .collect::>(); - let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight); - let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply); - if let Some(fill) = to_fill { - initial_balances.push(Cw20Coin { - address: "filler".to_string(), - amount: fill, - }) - } - - let pre_propose_info = get_pre_propose_info(&mut app, deposit_info, false); - - let proposer = match votes.first() { - Some(vote) => vote.voter.clone(), - None => panic!("do_test_votes must have at least one vote."), - }; - - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - veto: None, - threshold, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info, - }; - - let core_addr = setup_governance(&mut app, instantiate, Some(initial_balances)); - - let governance_modules: Vec = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(governance_modules.len(), 1); - let proposal_single = governance_modules.into_iter().next().unwrap().address; - - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &proposal_single); - // Pay the cw20 deposit if needed. - if let Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - amount, - .. - }) = deposit_config.deposit_info - { - app.execute_contract( - Addr::unchecked(&proposer), - token.clone(), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: pre_propose_module.to_string(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - } - - let funds = if let Some(CheckedDepositInfo { - denom: CheckedDenom::Native(ref denom), - amount, - .. - }) = deposit_config.deposit_info - { - // Mint the needed tokens to create the deposit. - app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { - to_address: proposer.clone(), - amount: coins(amount.u128(), denom), - })) - .unwrap(); - coins(amount.u128(), denom) - } else { - vec![] - }; - - app.execute_contract( - Addr::unchecked(&proposer), - pre_propose_module, - &cppbps::ExecuteMsg::Propose { - msg: cppbps::ProposeMessage::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - msgs: vec![], - }, - }, - &funds, - ) - .unwrap(); - - // Cast votes. - for vote in votes { - let TestSingleChoiceVote { - voter, - position, - weight, - should_execute, - } = vote; - // Vote on the proposal. - let res = app.execute_contract( - Addr::unchecked(voter.clone()), - proposal_single.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: position, - rationale: None, - }, - &[], - ); - match should_execute { - ShouldExecute::Yes => { - assert!(res.is_ok()); - // Check that the vote was recorded correctly. - let vote: VoteResponse = app - .wrap() - .query_wasm_smart( - proposal_single.clone(), - &QueryMsg::GetVote { - proposal_id: 1, - voter: voter.clone(), - }, - ) - .unwrap(); - let expected = VoteResponse { - vote: Some(VoteInfo { - rationale: None, - voter: Addr::unchecked(&voter), - vote: position, - power: match deposit_config.deposit_info { - Some(CheckedDepositInfo { - amount, - denom: CheckedDenom::Cw20(_), - .. - }) => { - if proposer == voter { - weight - amount - } else { - weight - } - } - // Native token deposits shouldn't impact - // expected voting power. - _ => weight, - }, - }), - }; - assert_eq!(vote, expected) - } - ShouldExecute::No => { - res.unwrap_err(); - } - ShouldExecute::Meh => (), - } - } - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: 1 }) - .unwrap(); - - // We just care about getting the right variant - assert_eq!( - discriminant::(&proposal.proposal.status), - discriminant::(&expected_status) - ); - - (app, core_addr) -} - -#[test] -fn test_vote_simple() { - dao_testing::test_simple_votes(do_votes_cw4_weights); - dao_testing::test_simple_votes(do_votes_staked_balances); - dao_testing::test_simple_votes(do_votes_nft_balances); - dao_testing::test_simple_votes(do_votes_native_staked_balances) -} - -#[test] -fn test_simple_vote_no_overflow() { - dao_testing::test_simple_vote_no_overflow(do_votes_staked_balances); - dao_testing::test_simple_vote_no_overflow(do_votes_native_staked_balances); -} - -#[test] -fn test_vote_no_overflow() { - dao_testing::test_vote_no_overflow(do_votes_staked_balances); - dao_testing::test_vote_no_overflow(do_votes_native_staked_balances); -} - -#[test] -fn test_simple_early_rejection() { - dao_testing::test_simple_early_rejection(do_votes_cw4_weights); - dao_testing::test_simple_early_rejection(do_votes_staked_balances); - dao_testing::test_simple_early_rejection(do_votes_native_staked_balances); -} - -#[test] -fn test_vote_abstain_only() { - dao_testing::test_vote_abstain_only(do_votes_cw4_weights); - dao_testing::test_vote_abstain_only(do_votes_staked_balances); - dao_testing::test_vote_abstain_only(do_votes_native_staked_balances); -} - -#[test] -fn test_tricky_rounding() { - dao_testing::test_tricky_rounding(do_votes_cw4_weights); - dao_testing::test_tricky_rounding(do_votes_staked_balances); - dao_testing::test_tricky_rounding(do_votes_native_staked_balances); -} - -#[test] -fn test_no_double_votes() { - dao_testing::test_no_double_votes(do_votes_cw4_weights); - dao_testing::test_no_double_votes(do_votes_staked_balances); - dao_testing::test_no_double_votes(do_votes_nft_balances); - dao_testing::test_no_double_votes(do_votes_native_staked_balances); -} - -#[test] -fn test_votes_favor_yes() { - dao_testing::test_votes_favor_yes(do_votes_staked_balances); - dao_testing::test_votes_favor_yes(do_votes_nft_balances); - dao_testing::test_votes_favor_yes(do_votes_native_staked_balances); -} - -#[test] -fn test_votes_low_threshold() { - dao_testing::test_votes_low_threshold(do_votes_cw4_weights); - dao_testing::test_votes_low_threshold(do_votes_staked_balances); - dao_testing::test_votes_low_threshold(do_votes_nft_balances); - dao_testing::test_votes_low_threshold(do_votes_native_staked_balances); -} - -#[test] -fn test_majority_vs_half() { - dao_testing::test_majority_vs_half(do_votes_cw4_weights); - dao_testing::test_majority_vs_half(do_votes_staked_balances); - dao_testing::test_majority_vs_half(do_votes_nft_balances); - dao_testing::test_majority_vs_half(do_votes_native_staked_balances); -} - -#[test] -fn test_pass_threshold_not_quorum() { - dao_testing::test_pass_threshold_not_quorum(do_votes_cw4_weights); - dao_testing::test_pass_threshold_not_quorum(do_votes_staked_balances); - dao_testing::test_pass_threshold_not_quorum(do_votes_nft_balances); - dao_testing::test_pass_threshold_not_quorum(do_votes_native_staked_balances); -} - -#[test] -fn test_pass_threshold_exactly_quorum() { - dao_testing::test_pass_exactly_quorum(do_votes_cw4_weights); - dao_testing::test_pass_exactly_quorum(do_votes_staked_balances); - dao_testing::test_pass_exactly_quorum(do_votes_nft_balances); - dao_testing::test_pass_exactly_quorum(do_votes_native_staked_balances); -} - -/// Generate some random voting selections and make sure they behave -/// as expected. We split this test up as these take a while and cargo -/// can parallize tests. -#[test] -fn fuzz_voting_cw4_weights() { - dao_testing::fuzz_voting(do_votes_cw4_weights) -} - -#[test] -fn fuzz_voting_staked_balances() { - dao_testing::fuzz_voting(do_votes_staked_balances) -} - -#[test] -fn fuzz_voting_native_staked_balances() { - dao_testing::fuzz_voting(do_votes_native_staked_balances) -} +// use std::mem::discriminant; + +// use cosmwasm_std::{coins, Addr, Coin, ContractInfo, Uint128}; +// use dao_interface::state::ProposalModule; +// use dao_pre_propose_single as cppbps; +// use secret_multi_test::{App, BankSudo, Executor, SudoMsg}; + +// use cw_denom::CheckedDenom; +// use dao_testing::{ShouldExecute, TestSingleChoiceVote}; +// use dao_voting::{ +// deposit::{CheckedDepositInfo, UncheckedDepositInfo}, +// status::Status, +// threshold::Threshold, +// }; +// use shade_protocol::utils::asset::RawContract; +// use snip20_reference_impl::msg::InitialBalance; + +// use crate::{ +// msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, +// query::{ProposalResponse, VoteInfo, VoteResponse}, +// testing::{instantiate::*, queries::query_deposit_config_and_pre_propose_module}, +// }; + +// pub(crate) fn do_votes_staked_balances( +// votes: Vec, +// threshold: Threshold, +// expected_status: Status, +// total_supply: Option, +// ) { +// do_test_votes( +// votes, +// threshold, +// expected_status, +// total_supply, +// None::, +// instantiate_with_staked_balances_governance, +// ); +// } + +// pub(crate) fn do_votes_nft_balances( +// votes: Vec, +// threshold: Threshold, +// expected_status: Status, +// total_supply: Option, +// ) { +// do_test_votes( +// votes, +// threshold, +// expected_status, +// total_supply, +// None, +// instantiate_with_staked_cw721_governance, +// ); +// } + +// pub(crate) fn do_votes_native_staked_balances( +// votes: Vec, +// threshold: Threshold, +// expected_status: Status, +// total_supply: Option, +// ) { +// do_test_votes( +// votes, +// threshold, +// expected_status, +// total_supply, +// None, +// instantiate_with_native_staked_balances_governance, +// ); +// } + +// pub(crate) fn do_votes_cw4_weights( +// votes: Vec, +// threshold: Threshold, +// expected_status: Status, +// total_supply: Option, +// ) { +// do_test_votes( +// votes, +// threshold, +// expected_status, +// total_supply, +// None::, +// instantiate_with_cw4_groups_governance, +// ); +// } + +// fn do_test_votes( +// votes: Vec, +// threshold: Threshold, +// expected_status: Status, +// total_supply: Option, +// deposit_info: Option, +// setup_governance: F, +// ) -> (App, Addr) +// where +// F: Fn(&mut App, InstantiateMsg, Option>) -> ContractInfo, +// { +// let mut app = App::default(); + +// // Mint some ujuno so that it exists for native staking tests +// // Otherwise denom validation will fail +// app.sudo(SudoMsg::Bank(BankSudo::Mint { +// to_address: "sodenomexists".to_string(), +// amount: vec![Coin { +// amount: Uint128::new(10), +// denom: "ujuno".to_string(), +// }], +// })) +// .unwrap(); + +// let mut initial_balances = votes +// .iter() +// .map( +// |TestSingleChoiceVote { voter, weight, .. }| InitialBalance { +// address: voter.to_string(), +// amount: *weight, +// }, +// ) +// .collect::>(); +// let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight); +// let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply); +// if let Some(fill) = to_fill { +// initial_balances.push(InitialBalance { +// address: "filler".to_string(), +// amount: fill, +// }) +// } + +// let pre_propose_info = get_pre_propose_info(&mut app, deposit_info, false); + +// let proposer = match votes.first() { +// Some(vote) => vote.voter.clone(), +// None => panic!("do_test_votes must have at least one vote."), +// }; + +// let query_auth = instantiate_query_auth(&mut app); + +// let max_voting_period = secret_utils::Duration::Height(6); +// let instantiate = InstantiateMsg { +// veto: None, +// threshold, +// max_voting_period, +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// close_proposal_on_execution_failure: true, +// pre_propose_info, +// dao_code_hash: "dao_code_hash".to_string(), +// query_auth: RawContract { +// address: query_auth.address.to_string(), +// code_hash: query_auth.code_hash, +// }, +// }; + +// let core_info = setup_governance(&mut app, instantiate, Some(initial_balances)); + +// let governance_modules: Vec = app +// .wrap() +// .query_wasm_smart( +// core_info.clone().code_hash, +// core_info.clone().address.to_string(), +// &dao_interface::msg::QueryMsg::ProposalModules { +// start_after: None, +// limit: None, +// }, +// ) +// .unwrap(); + +// assert_eq!(governance_modules.len(), 1); +// let proposal_single = governance_modules.into_iter().next().unwrap().address; + +// let (deposit_config, pre_propose_module) = +// query_deposit_config_and_pre_propose_module(&app, &proposal_single); +// // Pay the cw20 deposit if needed. +// if let Some(CheckedDepositInfo { +// denom: CheckedDenom::Cw20(ref token), +// amount, +// .. +// }) = deposit_config.deposit_info +// { +// app.execute_contract( +// Addr::unchecked(&proposer), +// token.clone(), +// &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { +// spender: pre_propose_module.to_string(), +// amount, +// expiration: None, +// padding: None, +// }, +// &[], +// ) +// .unwrap(); +// } + +// let funds = if let Some(CheckedDepositInfo { +// denom: CheckedDenom::Native(ref denom), +// amount, +// .. +// }) = deposit_config.deposit_info +// { +// // Mint the needed tokens to create the deposit. +// app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { +// to_address: proposer.clone(), +// amount: coins(amount.u128(), denom), +// })) +// .unwrap(); +// coins(amount.u128(), denom) +// } else { +// vec![] +// }; + +// app.execute_contract( +// Addr::unchecked(&proposer), +// pre_propose_module, +// &cppbps::ExecuteMsg::Propose { +// msg: cppbps::ProposeMessage::Propose { +// title: "A simple text proposal".to_string(), +// description: "This is a simple text proposal".to_string(), +// msgs: vec![], +// }, +// }, +// &funds, +// ) +// .unwrap(); + +// // Cast votes. +// for vote in votes { +// let TestSingleChoiceVote { +// voter, +// position, +// weight, +// should_execute, +// } = vote; +// // Vote on the proposal. +// let res = app.execute_contract( +// Addr::unchecked(voter.clone()), +// proposal_single.clone(), +// &ExecuteMsg::Vote { +// proposal_id: 1, +// vote: position, +// rationale: None, +// }, +// &[], +// ); +// match should_execute { +// ShouldExecute::Yes => { +// assert!(res.is_ok()); +// // Check that the vote was recorded correctly. +// let vote: VoteResponse = app +// .wrap() +// .query_wasm_smart( +// proposal_single.clone(), +// &QueryMsg::GetVote { +// proposal_id: 1, +// voter: voter.clone(), +// }, +// ) +// .unwrap(); +// let expected = VoteResponse { +// vote: Some(VoteInfo { +// rationale: None, +// voter: Addr::unchecked(&voter), +// vote: position, +// power: match deposit_config.deposit_info { +// Some(CheckedDepositInfo { +// amount, +// denom: CheckedDenom::Cw20(_), +// .. +// }) => { +// if proposer == voter { +// weight - amount +// } else { +// weight +// } +// } +// // Native token deposits shouldn't impact +// // expected voting power. +// _ => weight, +// }, +// }), +// }; +// assert_eq!(vote, expected) +// } +// ShouldExecute::No => { +// res.unwrap_err(); +// } +// ShouldExecute::Meh => (), +// } +// } + +// let proposal: ProposalResponse = app +// .wrap() +// .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: 1 }) +// .unwrap(); + +// // We just care about getting the right variant +// assert_eq!( +// discriminant::(&proposal.proposal.status), +// discriminant::(&expected_status) +// ); + +// (app, core_addr) +// } + +// #[test] +// fn test_vote_simple() { +// dao_testing::test_simple_votes(do_votes_cw4_weights); +// dao_testing::test_simple_votes(do_votes_staked_balances); +// dao_testing::test_simple_votes(do_votes_nft_balances); +// dao_testing::test_simple_votes(do_votes_native_staked_balances) +// } + +// #[test] +// fn test_simple_vote_no_overflow() { +// dao_testing::test_simple_vote_no_overflow(do_votes_staked_balances); +// dao_testing::test_simple_vote_no_overflow(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_vote_no_overflow() { +// dao_testing::test_vote_no_overflow(do_votes_staked_balances); +// dao_testing::test_vote_no_overflow(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_simple_early_rejection() { +// dao_testing::test_simple_early_rejection(do_votes_cw4_weights); +// dao_testing::test_simple_early_rejection(do_votes_staked_balances); +// dao_testing::test_simple_early_rejection(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_vote_abstain_only() { +// dao_testing::test_vote_abstain_only(do_votes_cw4_weights); +// dao_testing::test_vote_abstain_only(do_votes_staked_balances); +// dao_testing::test_vote_abstain_only(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_tricky_rounding() { +// dao_testing::test_tricky_rounding(do_votes_cw4_weights); +// dao_testing::test_tricky_rounding(do_votes_staked_balances); +// dao_testing::test_tricky_rounding(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_no_double_votes() { +// dao_testing::test_no_double_votes(do_votes_cw4_weights); +// dao_testing::test_no_double_votes(do_votes_staked_balances); +// dao_testing::test_no_double_votes(do_votes_nft_balances); +// dao_testing::test_no_double_votes(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_votes_favor_yes() { +// dao_testing::test_votes_favor_yes(do_votes_staked_balances); +// dao_testing::test_votes_favor_yes(do_votes_nft_balances); +// dao_testing::test_votes_favor_yes(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_votes_low_threshold() { +// dao_testing::test_votes_low_threshold(do_votes_cw4_weights); +// dao_testing::test_votes_low_threshold(do_votes_staked_balances); +// dao_testing::test_votes_low_threshold(do_votes_nft_balances); +// dao_testing::test_votes_low_threshold(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_majority_vs_half() { +// dao_testing::test_majority_vs_half(do_votes_cw4_weights); +// dao_testing::test_majority_vs_half(do_votes_staked_balances); +// dao_testing::test_majority_vs_half(do_votes_nft_balances); +// dao_testing::test_majority_vs_half(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_pass_threshold_not_quorum() { +// dao_testing::test_pass_threshold_not_quorum(do_votes_cw4_weights); +// dao_testing::test_pass_threshold_not_quorum(do_votes_staked_balances); +// dao_testing::test_pass_threshold_not_quorum(do_votes_nft_balances); +// dao_testing::test_pass_threshold_not_quorum(do_votes_native_staked_balances); +// } + +// #[test] +// fn test_pass_threshold_exactly_quorum() { +// dao_testing::test_pass_exactly_quorum(do_votes_cw4_weights); +// dao_testing::test_pass_exactly_quorum(do_votes_staked_balances); +// dao_testing::test_pass_exactly_quorum(do_votes_nft_balances); +// dao_testing::test_pass_exactly_quorum(do_votes_native_staked_balances); +// } + +// /// Generate some random voting selections and make sure they behave +// /// as expected. We split this test up as these take a while and cargo +// /// can parallize tests. +// #[test] +// fn fuzz_voting_cw4_weights() { +// dao_testing::fuzz_voting(do_votes_cw4_weights) +// } + +// #[test] +// fn fuzz_voting_staked_balances() { +// dao_testing::fuzz_voting(do_votes_staked_balances) +// } + +// #[test] +// fn fuzz_voting_native_staked_balances() { +// dao_testing::fuzz_voting(do_votes_native_staked_balances) +// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/execute.rs b/contracts/proposal/dao-proposal-single/src/testing/execute.rs index 657d228..a62590c 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/execute.rs @@ -1,23 +1,21 @@ -use cosmwasm_std::{coins, Addr, Coin, CosmosMsg, Uint128}; -use cw_multi_test::{App, BankSudo, Executor}; - -use cw_denom::CheckedDenom; -use dao_pre_propose_single as cppbps; -use dao_voting::{ - deposit::CheckedDepositInfo, pre_propose::ProposalCreationPolicy, - proposal::SingleChoiceProposeMsg as ProposeMsg, voting::Vote, +use cosmwasm_std::{ + from_binary, to_binary, Addr, Coin, ContractInfo, CosmosMsg, Decimal, MessageInfo, Uint128, }; +use secret_multi_test::{App, BankSudo, Executor}; + +use dao_voting::voting::Vote; +use secret_utils::Duration; +use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; +use snip20_reference_impl::msg::InitialBalance; use crate::{ msg::{ExecuteMsg, QueryMsg}, query::ProposalResponse, - testing::queries::{query_creation_policy, query_next_proposal_id}, + testing::queries::query_next_proposal_id, ContractError, }; -use super::{ - contracts::cw20_base_contract, queries::query_pre_proposal_single_config, CREATOR_ADDR, -}; +use super::{contracts::snip20_base_contract, CREATOR_ADDR}; // Creates a proposal then checks that the proposal was created with // the specified messages and returns the ID of the proposal. @@ -27,104 +25,78 @@ use super::{ pub(crate) fn make_proposal( app: &mut App, proposal_single: &Addr, - proposer: &str, + proposal_single_code_hash: String, + auth: Auth, msgs: Vec, -) -> u64 { - let proposal_creation_policy = query_creation_policy(app, proposal_single); - - // Collect the funding. - let funds = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => vec![], - ProposalCreationPolicy::Module { - addr: ref pre_propose, - } => { - let deposit_config = query_pre_proposal_single_config(app, pre_propose); - match deposit_config.deposit_info { - Some(CheckedDepositInfo { - denom, - amount, - refund_policy: _, - }) => match denom { - CheckedDenom::Native(denom) => coins(amount.u128(), denom), - CheckedDenom::Cw20(addr) => { - // Give an allowance, no funds. - app.execute_contract( - Addr::unchecked(proposer), - addr, - &cw20::Cw20ExecuteMsg::IncreaseAllowance { - spender: pre_propose.to_string(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - vec![] - } - }, - None => vec![], - } +) { + // let proposal_creation_policy = + // query_creation_policy(app, proposal_single, proposal_single_code_hash.clone()); + let mut proposer = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + proposer = Addr::unchecked(address); } - }; + _ => (), + } - // Make the proposal. - match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => app - .execute_contract( - Addr::unchecked(proposer), - proposal_single.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: msgs.clone(), - proposer: None, - }), - &[], - ) - .unwrap(), - ProposalCreationPolicy::Module { addr } => app - .execute_contract( - Addr::unchecked(proposer), - addr, - &cppbps::ExecuteMsg::Propose { - msg: cppbps::ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - msgs: msgs.clone(), - }, - }, - &funds, - ) - .unwrap(), - }; - let id = query_next_proposal_id(app, proposal_single); + app.execute_contract( + Addr::unchecked(proposer.clone()), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash.clone(), + }, + &ExecuteMsg::Propose(dao_voting::proposal::SingleChoiceProposeMsg { + title: "title".to_string(), + description: "description".to_string(), + msgs: msgs.clone(), + proposer: None, + }), + &[], + ) + .unwrap(); + + let id = query_next_proposal_id(app, proposal_single, proposal_single_code_hash.clone()); let id = id - 1; // Check that the proposal was created as expected. let proposal: ProposalResponse = app .wrap() - .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: id }) + .query_wasm_smart( + proposal_single_code_hash, + proposal_single, + &QueryMsg::Proposal { proposal_id: id }, + ) .unwrap(); assert_eq!(proposal.proposal.proposer, Addr::unchecked(proposer)); assert_eq!(proposal.proposal.title, "title".to_string()); assert_eq!(proposal.proposal.description, "description".to_string()); assert_eq!(proposal.proposal.msgs, msgs); - - id } -pub(crate) fn vote_on_proposal( +pub(crate) fn _vote_on_proposal( app: &mut App, proposal_single: &Addr, - sender: &str, + proposal_single_code_hash: String, + auth: Auth, proposal_id: u64, vote: Vote, ) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash.clone(), + }, &ExecuteMsg::Vote { + auth, proposal_id, vote, rationale: None, @@ -137,14 +109,26 @@ pub(crate) fn vote_on_proposal( pub(crate) fn vote_on_proposal_should_fail( app: &mut App, proposal_single: &Addr, - sender: &str, + proposal_single_code_hash: String, + auth: Auth, proposal_id: u64, vote: Vote, ) -> ContractError { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, &ExecuteMsg::Vote { + auth, proposal_id, vote, rationale: None, @@ -159,13 +143,25 @@ pub(crate) fn vote_on_proposal_should_fail( pub(crate) fn execute_proposal_should_fail( app: &mut App, proposal_single: &Addr, - sender: &str, + proposal_single_code_hash: String, + auth: Auth, proposal_id: u64, ) -> ContractError { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), - &ExecuteMsg::Execute { proposal_id }, + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::Execute { auth, proposal_id }, &[], ) .unwrap_err() @@ -173,18 +169,31 @@ pub(crate) fn execute_proposal_should_fail( .unwrap() } -pub(crate) fn vote_on_proposal_with_rationale( +pub(crate) fn _vote_on_proposal_with_rationale( app: &mut App, proposal_single: &Addr, - sender: &str, + proposal_single_code_hash: String, + auth: Auth, proposal_id: u64, vote: Vote, rationale: Option, ) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, &ExecuteMsg::Vote { + auth, proposal_id, vote, rationale, @@ -197,13 +206,17 @@ pub(crate) fn vote_on_proposal_with_rationale( pub(crate) fn update_rationale( app: &mut App, proposal_single: &Addr, + proposal_single_code_hash: String, sender: &str, proposal_id: u64, rationale: Option, ) { app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, &ExecuteMsg::UpdateRationale { proposal_id, rationale, @@ -213,16 +226,28 @@ pub(crate) fn update_rationale( .unwrap(); } -pub(crate) fn execute_proposal( +pub(crate) fn _execute_proposal( app: &mut App, proposal_single: &Addr, - sender: &str, + proposal_single_code_hash: String, + auth: Auth, proposal_id: u64, ) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), - &ExecuteMsg::Execute { proposal_id }, + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::Execute { auth, proposal_id }, &[], ) .unwrap(); @@ -231,12 +256,16 @@ pub(crate) fn execute_proposal( pub(crate) fn close_proposal_should_fail( app: &mut App, proposal_single: &Addr, + proposal_single_code_hash: String, sender: &str, proposal_id: u64, ) -> ContractError { app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, &ExecuteMsg::Close { proposal_id }, &[], ) @@ -245,67 +274,215 @@ pub(crate) fn close_proposal_should_fail( .unwrap() } -pub(crate) fn close_proposal( +pub(crate) fn _close_proposal( app: &mut App, proposal_single: &Addr, + proposal_single_code_hash: String, sender: &str, proposal_id: u64, ) { app.execute_contract( Addr::unchecked(sender), - proposal_single.clone(), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, &ExecuteMsg::Close { proposal_id }, &[], ) .unwrap(); } -pub(crate) fn mint_natives(app: &mut App, receiver: &str, amount: Vec) { - app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { +pub(crate) fn update_config( + app: &mut App, + proposal_single: &Addr, + proposal_single_code_hash: String, + sender: &str, + query_auth: RawContract, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::UpdateConfig { + threshold: dao_voting::threshold::Threshold::ThresholdQuorum { + quorum: dao_voting::threshold::PercentageThreshold::Percent(Decimal::percent(15)), + threshold: dao_voting::threshold::PercentageThreshold::Majority {}, + }, + max_voting_period: Duration::Time(604800), // One week. + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: "dao_address".to_string(), + code_hash: "dao_code_hash".to_string(), + close_proposal_on_execution_failure: true, + veto: None, + query_auth, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn update_config_should_fail( + app: &mut App, + proposal_single: &Addr, + proposal_single_code_hash: String, + sender: &str, + query_auth: RawContract, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::UpdateConfig { + threshold: dao_voting::threshold::Threshold::ThresholdQuorum { + quorum: dao_voting::threshold::PercentageThreshold::Percent(Decimal::percent(15)), + threshold: dao_voting::threshold::PercentageThreshold::Majority {}, + }, + max_voting_period: Duration::Time(604800), // One week. + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: "dao_address".to_string(), + code_hash: "dao_code_hash".to_string(), + close_proposal_on_execution_failure: true, + veto: None, + query_auth, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn update_pre_propose_info( + app: &mut App, + proposal_single: &Addr, + proposal_single_code_hash: String, + sender: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::UpdatePreProposeInfo { + info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn update_pre_propose_info_should_fail( + app: &mut App, + proposal_single: &Addr, + proposal_single_code_hash: String, + sender: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::UpdatePreProposeInfo { + info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn execute_veto_fails( + app: &mut App, + proposal_single: &Addr, + proposal_single_code_hash: String, + sender: &str, + proposal_id: u64, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_single.clone(), + code_hash: proposal_single_code_hash, + }, + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn _mint_natives(app: &mut App, receiver: &str, amount: Vec) { + app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { to_address: receiver.to_string(), amount, })) .unwrap(); } -pub(crate) fn mint_cw20s( +pub(crate) fn _mint_snip20s( app: &mut App, - cw20_contract: &Addr, + snip20_contract: &Addr, + snip20_contract_code_hash: String, sender: &Addr, receiver: &str, amount: u128, ) { app.execute_contract( sender.clone(), - cw20_contract.clone(), - &cw20::Cw20ExecuteMsg::Mint { + &ContractInfo { + address: snip20_contract.clone(), + code_hash: snip20_contract_code_hash, + }, + &snip20_reference_impl::msg::ExecuteMsg::Mint { recipient: receiver.to_string(), amount: Uint128::new(amount), + memo: None, + decoys: None, + entropy: None, + padding: None, }, &[], ) .unwrap(); } -pub(crate) fn instantiate_cw20_base_default(app: &mut App) -> Addr { - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_instantiate = cw20_base::msg::InstantiateMsg { - name: "cw20 token".to_string(), +pub(crate) fn _instantiate_snip20_base_default( + app: &mut App, + admin: Option, +) -> ContractInfo { + let snip20_contract_instantiate_info = app.store_code(snip20_base_contract()); + let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { + name: "snip20 token".to_string(), symbol: "cwtwenty".to_string(), decimals: 6, - initial_balances: vec![cw20::Cw20Coin { + initial_balances: Some(vec![InitialBalance { address: CREATOR_ADDR.to_string(), amount: Uint128::new(10_000_000), - }], - mint: None, - marketing: None, + }]), + admin, + prng_seed: to_binary(&"prng_seed".to_string()).unwrap(), + config: None, + supported_denoms: None, }; app.instantiate_contract( - cw20_id, + snip20_contract_instantiate_info, Addr::unchecked("ekez"), - &cw20_instantiate, + &snip20_instantiate, &[], - "cw20-base", + "snip20-base", None, ) .unwrap() @@ -314,14 +491,20 @@ pub(crate) fn instantiate_cw20_base_default(app: &mut App) -> Addr { pub(crate) fn add_proposal_hook( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::AddProposalHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -331,14 +514,20 @@ pub(crate) fn add_proposal_hook( pub(crate) fn add_proposal_hook_should_fail( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) -> ContractError { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::AddProposalHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -350,14 +539,20 @@ pub(crate) fn add_proposal_hook_should_fail( pub(crate) fn remove_proposal_hook( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::RemoveProposalHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -367,14 +562,20 @@ pub(crate) fn remove_proposal_hook( pub(crate) fn remove_proposal_hook_should_fail( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) -> ContractError { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::RemoveProposalHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -383,12 +584,23 @@ pub(crate) fn remove_proposal_hook_should_fail( .unwrap() } -pub(crate) fn add_vote_hook(app: &mut App, proposal_module: &Addr, sender: &str, hook_addr: &str) { +pub(crate) fn add_vote_hook( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::AddVoteHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -398,14 +610,20 @@ pub(crate) fn add_vote_hook(app: &mut App, proposal_module: &Addr, sender: &str, pub(crate) fn add_vote_hook_should_fail( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) -> ContractError { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::AddVoteHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -417,14 +635,20 @@ pub(crate) fn add_vote_hook_should_fail( pub(crate) fn remove_vote_hook( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::RemoveVoteHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -434,14 +658,20 @@ pub(crate) fn remove_vote_hook( pub(crate) fn remove_vote_hook_should_fail( app: &mut App, proposal_module: &Addr, + proposal_module_code_hash: String, sender: &str, hook_addr: &str, + hook_code_hash: &str, ) -> ContractError { app.execute_contract( Addr::unchecked(sender), - proposal_module.clone(), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, &ExecuteMsg::RemoveVoteHook { address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), }, &[], ) @@ -449,3 +679,27 @@ pub(crate) fn remove_vote_hook_should_fail( .downcast() .unwrap() } + +pub(crate) fn create_viewing_key( + app: &mut App, + contract_info: ContractInfo, + info: MessageInfo, +) -> String { + let msg = shade_protocol::contract_interfaces::query_auth::ExecuteMsg::CreateViewingKey { + entropy: "entropy".to_string(), + padding: None, + }; + let res = app + .execute_contract(info.sender, &contract_info, &msg, &[]) + .unwrap(); + let mut viewing_key = String::new(); + let data: shade_protocol::contract_interfaces::query_auth::ExecuteAnswer = + from_binary(&res.data.unwrap()).unwrap(); + if let shade_protocol::contract_interfaces::query_auth::ExecuteAnswer::CreateViewingKey { + key, + } = data + { + viewing_key = key; + }; + viewing_key +} diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 0201547..1055cba 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -1,25 +1,29 @@ -use cosmwasm_std::{to_json_binary, Addr, Coin, Decimal, Empty, Uint128}; -use cw20::Cw20Coin; +use cosmwasm_std::{to_binary, Addr, Coin, ContractInfo, Decimal, Empty, Uint128}; -use cw_multi_test::{next_block, App, BankSudo, Executor, SudoMsg}; -use cw_utils::Duration; -use dao_interface::state::{Admin, ModuleInstantiateInfo}; +use dao_interface::state::{Admin, AnyContractInfo, ModuleInstantiateInfo}; use dao_pre_propose_single as cppbps; +use secret_multi_test::{next_block, App, BankSudo, ContractInstantiationInfo, Executor, SudoMsg}; +use secret_utils::Duration; use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, + deposit::UncheckedDepositInfo, pre_propose::PreProposeInfo, threshold::{ActiveThreshold, PercentageThreshold, Threshold::ThresholdQuorum}, }; use dao_voting_cw4::msg::GroupContract; +use dao_voting_snip20_staked::snip20_msg::InitialBalance as InitialBalanceSnip20Staked; +use shade_protocol::utils::asset::RawContract; +use snip20_reference_impl::msg::InitialBalance; +use snip721_reference_impl::msg::ReceiverInfo; use crate::msg::InstantiateMsg; use super::{ contracts::{ - cw20_base_contract, cw20_stake_contract, cw20_staked_balances_voting_contract, - cw4_group_contract, cw4_voting_contract, cw721_base_contract, cw721_stake_contract, - cw_core_contract, native_staked_balances_voting_contract, proposal_single_contract, + cw4_group_contract, cw4_voting_contract, cw_core_contract, + native_staked_balances_voting_contract, proposal_single_contract, query_auth_contract, + snip20_base_contract, snip20_stake_contract, snip20_staked_balances_voting_contract, + snip721_base_contract, snip721_stake_contract, }, CREATOR_ADDR, }; @@ -28,16 +32,19 @@ pub(crate) fn get_pre_propose_info( app: &mut App, deposit_info: Option, open_proposal_submission: bool, + proposal_module_code_hash: String, ) -> PreProposeInfo { let pre_propose_contract = app.store_code(crate::testing::contracts::pre_propose_single_contract()); PreProposeInfo::ModuleMayPropose { info: ModuleInstantiateInfo { - code_id: pre_propose_contract, - msg: to_json_binary(&cppbps::InstantiateMsg { + code_id: pre_propose_contract.code_id, + code_hash: pre_propose_contract.code_hash, + msg: to_binary(&cppbps::InstantiateMsg { deposit_info, open_proposal_submission, extension: Empty::default(), + proposal_module_code_hash, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -47,7 +54,10 @@ pub(crate) fn get_pre_propose_info( } } -pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) -> InstantiateMsg { +pub(crate) fn get_default_token_dao_proposal_module_instantiate( + query_auth: RawContract, + dao_code_hash: String, +) -> InstantiateMsg { InstantiateMsg { veto: None, threshold: ThresholdQuorum { @@ -58,24 +68,19 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) - min_voting_period: None, only_members_execute: true, allow_revoting: false, - pre_propose_info: get_pre_propose_info( - app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(10_000_000), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, close_proposal_on_execution_failure: true, + dao_code_hash, + query_auth, } } // Same as above but no proposal deposit. -pub(crate) fn get_default_non_token_dao_proposal_module_instantiate( +pub(crate) fn _get_default_non_token_dao_proposal_module_instantiate( app: &mut App, + proposal_module_code_hash: String, + query_auth: RawContract, + dao_code_hash: String, ) -> InstantiateMsg { InstantiateMsg { veto: None, @@ -87,30 +92,34 @@ pub(crate) fn get_default_non_token_dao_proposal_module_instantiate( min_voting_period: None, only_members_execute: true, allow_revoting: false, - pre_propose_info: get_pre_propose_info(app, None, false), + pre_propose_info: get_pre_propose_info(app, None, false, proposal_module_code_hash), close_proposal_on_execution_failure: true, + dao_code_hash, + query_auth, } } -pub(crate) fn instantiate_with_staked_cw721_governance( +pub(crate) fn _instantiate_with_staked_snip721_governance( app: &mut App, proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_single_contract()); + initial_balances: Option>, + dao_code_hash: String, + query_auth: RawContract, +) -> ContractInfo { + let proposal_module_info = app.store_code(proposal_single_contract()); let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { + vec![InitialBalance { address: CREATOR_ADDR.to_string(), amount: Uint128::new(100_000_000), }] }); - let initial_balances: Vec = { + let initial_balances: Vec = { let mut already_seen = vec![]; initial_balances .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { + .filter(|InitialBalance { address, amount: _ }| { if already_seen.contains(address) { false } else { @@ -121,18 +130,22 @@ pub(crate) fn instantiate_with_staked_cw721_governance( .collect() }; - let cw721_id = app.store_code(cw721_base_contract()); - let cw721_stake_id = app.store_code(cw721_stake_contract()); - let core_contract_id = app.store_code(cw_core_contract()); + let snip721_info = app.store_code(snip721_base_contract()); + let snip721_stake_info = app.store_code(snip721_stake_contract()); + let core_contract_info = app.store_code(cw_core_contract()); - let nft_address = app + let nft_info = app .instantiate_contract( - cw721_id, + snip721_info, Addr::unchecked("ekez"), - &cw721_base::msg::InstantiateMsg { - minter: "ekez".to_string(), + &snip721_reference_impl::msg::InstantiateMsg { + admin: Some("ekez".to_string()), symbol: "token".to_string(), name: "ekez token best token".to_string(), + entropy: "entropy".to_string(), + royalty_info: None, + config: None, + post_init_callback: None, }, &[], "nft-staking", @@ -146,15 +159,19 @@ pub(crate) fn instantiate_with_staked_cw721_governance( description: "A DAO that builds DAOs".to_string(), dao_uri: None, image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, + automatically_add_snip20s: true, + automatically_add_snip721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: cw721_stake_id, - msg: to_json_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { + code_id: snip721_stake_info.code_id, + code_hash: snip721_stake_info.code_hash, + msg: to_binary(&dao_voting_snip721_staked::msg::InstantiateMsg { unstaking_duration: None, - nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { - address: nft_address.to_string(), + nft_contract: dao_voting_snip721_staked::msg::NftContract::Existing { + address: nft_info.clone().address.to_string(), + code_hash: nft_info.clone().code_hash, }, + dao_code_hash, + query_auth, active_threshold: None, }) .unwrap(), @@ -163,18 +180,21 @@ pub(crate) fn instantiate_with_staked_cw721_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), + code_id: proposal_module_info.code_id, + code_hash: proposal_module_info.code_hash, + msg: to_binary(&proposal_module_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], label: "DAO DAO governance module.".to_string(), }], initial_items: None, + snip20_code_hash: "".to_string(), + snip721_code_hash: "".to_string(), }; - let core_addr = app + let core_contract = app .instantiate_contract( - core_contract_id, + core_contract_info, Addr::unchecked(CREATOR_ADDR), &instantiate_core, &[], @@ -186,33 +206,45 @@ pub(crate) fn instantiate_with_staked_cw721_governance( let core_state: dao_interface::query::DumpStateResponse = app .wrap() .query_wasm_smart( - core_addr.clone(), + core_contract.clone().code_hash, + core_contract.clone().address.to_string(), &dao_interface::msg::QueryMsg::DumpState {}, ) .unwrap(); - let staking_addr = core_state.voting_module; + let staking_info = core_state.voting_module; - for Cw20Coin { address, amount } in initial_balances { + for InitialBalance { address, amount } in initial_balances { for i in 0..amount.u128() { app.execute_contract( Addr::unchecked("ekez"), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::, Empty>::Mint { - token_id: format!("{address}_{i}"), - owner: address.clone(), - token_uri: None, - extension: None, + &nft_info.clone(), + &snip721_reference_impl::msg::ExecuteMsg::MintNft { + token_id: Some(format!("{address}_{i}")), + owner: Some(address.clone()), + public_metadata: None, + private_metadata: None, + serial_number: None, + royalty_info: None, + transferable: Some(true), + memo: None, + padding: None, }, &[], ) .unwrap(); app.execute_contract( Addr::unchecked(address.clone()), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::SendNft::, Empty> { - contract: staking_addr.to_string(), + &nft_info.clone(), + &snip721_reference_impl::msg::ExecuteMsg::SendNft { + contract: staking_info.clone().addr.to_string(), token_id: format!("{address}_{i}"), - msg: to_json_binary("").unwrap(), + msg: Some(to_binary("").unwrap()), + receiver_info: Some(ReceiverInfo { + recipient_code_hash: staking_info.clone().code_hash, + also_implements_batch_receive_nft: Some(true), + }), + memo: None, + padding: None, }, &[], ) @@ -223,29 +255,31 @@ pub(crate) fn instantiate_with_staked_cw721_governance( // Update the block so that staked balances appear. app.update_block(|block| block.height += 1); - core_addr + core_contract } -pub(crate) fn instantiate_with_native_staked_balances_governance( +pub(crate) fn _instantiate_with_native_staked_balances_governance( app: &mut App, proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_single_contract()); + initial_balances: Option>, + query_auth: RawContract, + dao_code_hash: String, +) -> ContractInfo { + let proposal_module_instantiate_info = app.store_code(proposal_single_contract()); let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { + vec![InitialBalance { address: CREATOR_ADDR.to_string(), amount: Uint128::new(100_000_000), }] }); // Collapse balances so that we can test double votes. - let initial_balances: Vec = { + let initial_balances: Vec = { let mut already_seen = vec![]; initial_balances .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { + .filter(|InitialBalance { address, amount: _ }| { if already_seen.contains(address) { false } else { @@ -256,8 +290,8 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( .collect() }; - let native_stake_id = app.store_code(native_staked_balances_voting_contract()); - let core_contract_id = app.store_code(cw_core_contract()); + let native_stake_instantiate_info = app.store_code(native_staked_balances_voting_contract()); + let core_contract_instantiate_info = app.store_code(cw_core_contract()); let instantiate_core = dao_interface::msg::InstantiateMsg { admin: None, @@ -265,16 +299,19 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( description: "A DAO that builds DAOs".to_string(), dao_uri: None, image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, + automatically_add_snip20s: true, + automatically_add_snip721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: native_stake_id, - msg: to_json_binary(&dao_voting_token_staked::msg::InstantiateMsg { + code_id: native_stake_instantiate_info.code_id, + code_hash: native_stake_instantiate_info.code_hash, + msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { token_info: dao_voting_token_staked::msg::TokenInfo::Existing { denom: "ujuno".to_string(), }, unstaking_duration: None, active_threshold: None, + query_auth, + dao_code_hash, }) .unwrap(), admin: None, @@ -282,18 +319,21 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), + code_id: proposal_module_instantiate_info.code_id, + code_hash: proposal_module_instantiate_info.code_hash, + msg: to_binary(&proposal_module_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], label: "DAO DAO governance module.".to_string(), }], initial_items: None, + snip20_code_hash: "".to_string(), + snip721_code_hash: "".to_string(), }; - let core_addr = app + let core_contract = app .instantiate_contract( - core_contract_id, + core_contract_instantiate_info.clone(), Addr::unchecked(CREATOR_ADDR), &instantiate_core, &[], @@ -305,13 +345,14 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( let gov_state: dao_interface::query::DumpStateResponse = app .wrap() .query_wasm_smart( - core_addr.clone(), + core_contract.clone().code_hash, + core_contract.clone().address.to_string(), &dao_interface::msg::QueryMsg::DumpState {}, ) .unwrap(); - let native_staking_addr = gov_state.voting_module; + let native_staking_contract_info = gov_state.voting_module; - for Cw20Coin { address, amount } in initial_balances { + for InitialBalance { address, amount } in initial_balances { app.sudo(SudoMsg::Bank(BankSudo::Mint { to_address: address.clone(), amount: vec![Coin { @@ -322,7 +363,10 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( .unwrap(); app.execute_contract( Addr::unchecked(&address), - native_staking_addr.clone(), + &ContractInfo { + address: native_staking_contract_info.clone().addr, + code_hash: native_staking_contract_info.clone().code_hash, + }, &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, &[Coin { amount, @@ -334,29 +378,30 @@ pub(crate) fn instantiate_with_native_staked_balances_governance( app.update_block(next_block); - core_addr + core_contract } -pub(crate) fn instantiate_with_staked_balances_governance( +pub(crate) fn _instantiate_with_staked_balances_governance( app: &mut App, proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_single_contract()); - + initial_balances: Option>, + dao_contract_instantiate_info: ContractInstantiationInfo, + proposal_module_contract_info: ContractInstantiationInfo, + query_auth: RawContract, +) -> ContractInfo { let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { + vec![InitialBalance { address: CREATOR_ADDR.to_string(), amount: Uint128::new(100_000_000), }] }); // Collapse balances so that we can test double votes. - let initial_balances: Vec = { + let initial_balances: Vec = { let mut already_seen = vec![]; initial_balances .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { + .filter(|InitialBalance { address, amount: _ }| { if already_seen.contains(address) { false } else { @@ -367,10 +412,16 @@ pub(crate) fn instantiate_with_staked_balances_governance( .collect() }; - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_stake_id = app.store_code(cw20_stake_contract()); - let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); - let core_contract_id = app.store_code(cw_core_contract()); + let snip20_contract_info = instantiate_snip20(app, initial_balances.clone()); + let snip20_stake_contract_info = instantiate_staking( + app, + snip20_contract_info.clone().address, + snip20_contract_info.clone().code_hash, + Some(Duration::Height(6)), + query_auth.clone(), + ); + let staked_balances_voting_instantiate_info = + app.store_code(snip20_staked_balances_voting_contract()); let instantiate_core = dao_interface::msg::InstantiateMsg { admin: None, @@ -378,24 +429,26 @@ pub(crate) fn instantiate_with_staked_balances_governance( description: "A DAO that builds DAOs".to_string(), dao_uri: None, image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, + automatically_add_snip20s: true, + automatically_add_snip721s: false, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: staked_balances_voting_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { + code_id: staked_balances_voting_instantiate_info.code_id, + code_hash: staked_balances_voting_instantiate_info.code_hash, + msg: to_binary(&dao_voting_snip20_staked::msg::InstantiateMsg { active_threshold: None, - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token.".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: initial_balances.clone(), - marketing: None, - staking_code_id: cw20_stake_id, - unstaking_duration: Some(Duration::Height(6)), - initial_dao_balance: None, + token_info: dao_voting_snip20_staked::msg::Snip20TokenInfo::Existing { + address: snip20_contract_info.clone().address.to_string(), + code_hash: snip20_contract_info.clone().code_hash, + staking_contract: dao_voting_snip20_staked::msg::StakingInfo::Existing { + staking_contract_address: snip20_stake_contract_info + .clone() + .address + .to_string(), + staking_contract_code_hash: snip20_stake_contract_info.clone().code_hash, + }, }, + dao_code_hash: dao_contract_instantiate_info.clone().code_hash, + query_auth, }) .unwrap(), admin: None, @@ -403,18 +456,21 @@ pub(crate) fn instantiate_with_staked_balances_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), + code_id: proposal_module_contract_info.code_id, + code_hash: proposal_module_contract_info.code_hash, + msg: to_binary(&proposal_module_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], label: "DAO DAO governance module.".to_string(), }], initial_items: None, + snip20_code_hash: "".to_string(), + snip721_code_hash: "".to_string(), }; - let core_addr = app + let core_contract = app .instantiate_contract( - core_contract_id, + dao_contract_instantiate_info, Addr::unchecked(CREATOR_ADDR), &instantiate_core, &[], @@ -426,36 +482,47 @@ pub(crate) fn instantiate_with_staked_balances_governance( let gov_state: dao_interface::query::DumpStateResponse = app .wrap() .query_wasm_smart( - core_addr.clone(), + core_contract.clone().code_hash, + core_contract.clone().address.to_string(), &dao_interface::msg::QueryMsg::DumpState {}, ) .unwrap(); let voting_module = gov_state.voting_module; - let staking_contract: Addr = app + let staking_contract: AnyContractInfo = app .wrap() .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, + voting_module.clone().code_hash, + voting_module.clone().addr.to_string(), + &dao_voting_snip20_staked::msg::QueryMsg::StakingContract {}, ) .unwrap(); - let token_contract: Addr = app + let token_contract: AnyContractInfo = app .wrap() .query_wasm_smart( - voting_module, + voting_module.clone().code_hash, + voting_module.clone().addr.to_string(), &dao_interface::voting::Query::TokenContract {}, ) .unwrap(); // Stake all the initial balances. - for Cw20Coin { address, amount } in initial_balances { + for InitialBalance { address, amount } in initial_balances { app.execute_contract( Addr::unchecked(address), - token_contract.clone(), - &cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), + &ContractInfo { + address: token_contract.clone().addr, + code_hash: token_contract.clone().code_hash, + }, + &snip20_reference_impl::msg::ExecuteMsg::Send { + recipient: staking_contract.clone().addr.to_string(), + recipient_code_hash: Some(staking_contract.clone().code_hash), amount, - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), + msg: Some(to_binary(&snip20_stake::msg::ReceiveMsg::Stake {}).unwrap()), + decoys: None, + memo: None, + entropy: None, + padding: None, }, &[], ) @@ -465,23 +532,25 @@ pub(crate) fn instantiate_with_staked_balances_governance( // Update the block so that those staked balances appear. app.update_block(|block| block.height += 1); - core_addr + core_contract } -pub(crate) fn instantiate_with_staking_active_threshold( +pub(crate) fn _instantiate_with_staking_active_threshold( app: &mut App, proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, + initial_balances: Option>, active_threshold: Option, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_single_contract()); - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_staking_id = app.store_code(cw20_stake_contract()); - let core_id = app.store_code(cw_core_contract()); - let votemod_id = app.store_code(cw20_staked_balances_voting_contract()); + dao_code_hash: String, + query_auth: RawContract, +) -> ContractInfo { + let proposal_module_contract_instantiate_info = app.store_code(proposal_single_contract()); + let snip20_instantiate_info = app.store_code(snip20_base_contract()); + let snip20_staking_instantiate_info = app.store_code(snip20_stake_contract()); + let core_instantiate_info = app.store_code(cw_core_contract()); + let voting_instantiate_info = app.store_code(snip20_staked_balances_voting_contract()); let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { + vec![InitialBalanceSnip20Staked { address: CREATOR_ADDR.to_string(), amount: Uint128::new(100_000_000), }] @@ -493,23 +562,26 @@ pub(crate) fn instantiate_with_staking_active_threshold( description: "A DAO that builds DAOs".to_string(), dao_uri: None, image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: true, + automatically_add_snip20s: true, + automatically_add_snip721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: votemod_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token".to_string(), + code_id: voting_instantiate_info.code_id, + code_hash: voting_instantiate_info.code_hash, + msg: to_binary(&dao_voting_snip20_staked::msg::InstantiateMsg { + token_info: dao_voting_snip20_staked::msg::Snip20TokenInfo::New { + code_id: snip20_instantiate_info.code_id, + code_hash: snip20_instantiate_info.code_hash, name: "DAO".to_string(), symbol: "DAO".to_string(), decimals: 6, initial_balances, - marketing: None, - staking_code_id: cw20_staking_id, + staking_code_id: snip20_staking_instantiate_info.code_id, + staking_code_hash: snip20_staking_instantiate_info.code_hash, unstaking_duration: None, initial_dao_balance: None, }, + dao_code_hash, + query_auth, active_threshold, }) .unwrap(), @@ -518,17 +590,20 @@ pub(crate) fn instantiate_with_staking_active_threshold( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), + code_id: proposal_module_contract_instantiate_info.code_id, + code_hash: proposal_module_contract_instantiate_info.code_hash, + msg: to_binary(&proposal_module_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], label: "DAO DAO governance module".to_string(), }], initial_items: None, + snip20_code_hash: "".to_string(), + snip721_code_hash: "".to_string(), }; app.instantiate_contract( - core_id, + core_instantiate_info.clone(), Addr::unchecked(CREATOR_ADDR), &governance_instantiate, &[], @@ -538,18 +613,20 @@ pub(crate) fn instantiate_with_staking_active_threshold( .unwrap() } -pub(crate) fn instantiate_with_cw4_groups_governance( +pub(crate) fn _instantiate_with_cw4_groups_governance( app: &mut App, proposal_module_instantiate: InstantiateMsg, - initial_weights: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_single_contract()); - let cw4_id = app.store_code(cw4_group_contract()); - let core_id = app.store_code(cw_core_contract()); - let votemod_id = app.store_code(cw4_voting_contract()); + initial_weights: Option>, + query_auth: RawContract, + dao_code_hash: String, +) -> ContractInfo { + let proposal_module_contract_instantiate_info = app.store_code(proposal_single_contract()); + let cw4_instantiate_info = app.store_code(cw4_group_contract()); + let core_instantiate_info = app.store_code(cw_core_contract()); + let votemod_instantiate_info = app.store_code(cw4_voting_contract()); let initial_weights = initial_weights.unwrap_or_else(|| { - vec![Cw20Coin { + vec![InitialBalance { address: CREATOR_ADDR.to_string(), amount: Uint128::new(1), }] @@ -560,7 +637,7 @@ pub(crate) fn instantiate_with_cw4_groups_governance( let mut already_seen = vec![]; initial_weights .into_iter() - .filter(|Cw20Coin { address, .. }| { + .filter(|InitialBalance { address, .. }| { if already_seen.contains(address) { false } else { @@ -568,7 +645,7 @@ pub(crate) fn instantiate_with_cw4_groups_governance( true } }) - .map(|Cw20Coin { address, amount }| cw4::Member { + .map(|InitialBalance { address, amount }| cw4::Member { addr: address, weight: amount.u128() as u64, }) @@ -581,15 +658,19 @@ pub(crate) fn instantiate_with_cw4_groups_governance( description: "A DAO that builds DAOs".to_string(), dao_uri: None, image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: true, + automatically_add_snip20s: true, + automatically_add_snip721s: true, voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: votemod_id, - msg: to_json_binary(&dao_voting_cw4::msg::InstantiateMsg { + code_id: votemod_instantiate_info.code_id, + code_hash: votemod_instantiate_info.code_hash, + msg: to_binary(&dao_voting_cw4::msg::InstantiateMsg { group_contract: GroupContract::New { - cw4_group_code_id: cw4_id, + cw4_group_code_id: cw4_instantiate_info.code_id, + cw4_group_code_hash: cw4_instantiate_info.code_hash, initial_members: initial_weights, }, + query_auth, + dao_code_hash, }) .unwrap(), admin: Some(Admin::CoreModule {}), @@ -597,18 +678,21 @@ pub(crate) fn instantiate_with_cw4_groups_governance( label: "DAO DAO voting module".to_string(), }, proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), + code_id: proposal_module_contract_instantiate_info.code_id, + code_hash: proposal_module_contract_instantiate_info.code_hash, + msg: to_binary(&proposal_module_instantiate).unwrap(), admin: Some(Admin::CoreModule {}), funds: vec![], label: "DAO DAO governance module".to_string(), }], initial_items: None, + snip20_code_hash: "".to_string(), + snip721_code_hash: "".to_string(), }; - let addr = app + let core_contract = app .instantiate_contract( - core_id, + core_instantiate_info.clone(), Addr::unchecked(CREATOR_ADDR), &governance_instantiate, &[], @@ -620,5 +704,86 @@ pub(crate) fn instantiate_with_cw4_groups_governance( // Update the block so that weights appear. app.update_block(|block| block.height += 1); - addr + core_contract +} + +pub(crate) fn instantiate_query_auth(app: &mut App) -> ContractInfo { + let query_auth_info = app.store_code(query_auth_contract()); + let msg = shade_protocol::contract_interfaces::query_auth::InstantiateMsg { + admin_auth: shade_protocol::Contract { + address: Addr::unchecked("admin_contract"), + code_hash: "code_hash".to_string(), + }, + prng_seed: to_binary("seed").unwrap(), + }; + + app.instantiate_contract( + query_auth_info, + Addr::unchecked(CREATOR_ADDR), + &msg, + &[], + "query_auth", + None, + ) + .unwrap() +} + +pub(crate) fn instantiate_staking( + app: &mut App, + snip20: Addr, + snip20_code_hash: String, + unstaking_duration: Option, + query_auth: RawContract, +) -> ContractInfo { + let staking_info = app.store_code(snip20_stake_contract()); + let msg = snip20_stake::msg::InstantiateMsg { + owner: Some(CREATOR_ADDR.to_string()), + token_address: snip20.to_string(), + unstaking_duration, + token_code_hash: Some(snip20_code_hash), + query_auth, + }; + app.instantiate_contract( + staking_info, + Addr::unchecked(CREATOR_ADDR), + &msg, + &[], + "staking", + Some("admin".to_string()), + ) + .unwrap() +} + +pub(crate) fn instantiate_snip20( + app: &mut App, + initial_balances: Vec, +) -> ContractInfo { + let snip20_info = app.store_code(snip20_base_contract()); + let msg = snip20_reference_impl::msg::InstantiateMsg { + name: String::from("Test"), + symbol: String::from("TEST"), + decimals: 6, + initial_balances: Some(initial_balances), + admin: None, + prng_seed: to_binary("seed").unwrap(), + config: Some(snip20_reference_impl::msg::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: None, + enable_burn: None, + can_modify_denoms: None, + }), + supported_denoms: None, + }; + + app.instantiate_contract( + snip20_info, + Addr::unchecked(CREATOR_ADDR), + &msg, + &[], + "snip20", + None, + ) + .unwrap() } diff --git a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs index 8dab114..42720bf 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs @@ -1,478 +1,475 @@ -use cosmwasm_std::{to_json_binary, Addr, Uint128, WasmMsg}; -use cw20::Cw20Coin; -use cw_multi_test::{next_block, App, Executor}; -use cw_utils::Duration; -use dao_interface::query::{GetItemResponse, ProposalModuleCountResponse}; -use dao_testing::contracts::{ - cw20_base_contract, cw20_stake_contract, cw20_staked_balances_voting_contract, - dao_dao_contract, proposal_single_contract, v1_dao_dao_contract, v1_proposal_single_contract, -}; -use dao_voting::veto::VetoConfig; -use dao_voting::{ - deposit::{UncheckedDepositInfo, VotingModuleTokenType}, - status::Status, -}; +// use cosmwasm_std::{to_binary, Addr, Uint128, WasmMsg}; +// use secret_multi_test::{next_block, App, Executor}; +// use secret_utils::Duration; +// use dao_interface::query::{GetItemResponse, ProposalModuleCountResponse}; -use crate::testing::queries::query_list_proposals; -use crate::testing::{ - execute::{execute_proposal, make_proposal, vote_on_proposal}, - instantiate::get_pre_propose_info, - queries::{query_proposal, query_proposal_count}, -}; +// use dao_voting::veto::VetoConfig; +// use dao_voting::{ +// deposit::{UncheckedDepositInfo, VotingModuleTokenType}, +// status::Status, +// }; -/// This test attempts to simulate a realistic migration from DAO DAO -/// v1 to v2. Other tests in `/tests/tests.rs` check that versions and -/// top-level configs are updated correctly during migration. This -/// concerns itself more with more subtle state in the contracts that -/// is less functionality critical and thus more likely to be -/// overlooked in migration logic. -/// -/// - I can migrate with tokens in the treasury and completed -/// proposals. -/// -/// - I can migrate an open and unexecutable proposal, and use -/// `close_proposal_on_execution_failure` to close it once the -/// migration completes. -/// -/// - Proposal count remains accurate after proposal migration. -/// -/// - Items are not overriden during migration. -#[test] -fn test_v1_v2_full_migration() { - let sender = Addr::unchecked("sender"); +// use crate::testing::contracts::{v1_dao_dao_contract, v1_proposal_single_contract}; +// use crate::testing::queries::query_list_proposals; +// use crate::testing::{ +// execute::{execute_proposal, make_proposal, vote_on_proposal}, +// instantiate::get_pre_propose_info, +// queries::{query_proposal, query_proposal_count}, +// }; - let mut app = App::default(); +// /// This test attempts to simulate a realistic migration from DAO DAO +// /// v1 to v2. Other tests in `/tests/tests.rs` check that versions and +// /// top-level configs are updated correctly during migration. This +// /// concerns itself more with more subtle state in the contracts that +// /// is less functionality critical and thus more likely to be +// /// overlooked in migration logic. +// /// +// /// - I can migrate with tokens in the treasury and completed +// /// proposals. +// /// +// /// - I can migrate an open and unexecutable proposal, and use +// /// `close_proposal_on_execution_failure` to close it once the +// /// migration completes. +// /// +// /// - Proposal count remains accurate after proposal migration. +// /// +// /// - Items are not overriden during migration. +// #[test] +// fn test_v1_v2_full_migration() { +// let sender = Addr::unchecked("sender"); - // ---- - // instantiate a v1 DAO - // ---- +// let mut app = App::default(); - let proposal_code = app.store_code(v1_proposal_single_contract()); - let core_code = app.store_code(v1_dao_dao_contract()); +// // ---- +// // instantiate a v1 DAO +// // ---- - // cw20 staking and voting module has not changed across v1->v2 so - // we use the current edition. - let cw20_code = app.store_code(cw20_base_contract()); - let cw20_stake_code = app.store_code(cw20_stake_contract()); - let voting_code = app.store_code(cw20_staked_balances_voting_contract()); +// let proposal_code = app.store_code(v1_proposal_single_contract()); +// let core_code = app.store_code(v1_dao_dao_contract()); - let initial_balances = vec![Cw20Coin { - address: sender.to_string(), - amount: Uint128::new(2), - }]; +// // cw20 staking and voting module has not changed across v1->v2 so +// // we use the current edition. +// let cw20_code = app.store_code(cw20_base_contract()); +// let cw20_stake_code = app.store_code(cw20_stake_contract()); +// let voting_code = app.store_code(cw20_staked_balances_voting_contract()); - let core = app - .instantiate_contract( - core_code, - sender.clone(), - &cw_core_v1::msg::InstantiateMsg { - admin: Some(sender.to_string()), - name: "n".to_string(), - description: "d".to_string(), - image_url: Some("i".to_string()), - automatically_add_cw20s: false, - automatically_add_cw721s: true, - voting_module_instantiate_info: cw_core_v1::msg::ModuleInstantiateInfo { - code_id: voting_code, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - active_threshold: None, - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_code, - label: "token".to_string(), - name: "name".to_string(), - symbol: "symbol".to_string(), - decimals: 6, - initial_balances, - marketing: None, - staking_code_id: cw20_stake_code, - unstaking_duration: None, - initial_dao_balance: Some(Uint128::new(100)), - }, - }) - .unwrap(), - admin: cw_core_v1::msg::Admin::CoreContract {}, - label: "voting".to_string(), - }, - proposal_modules_instantiate_info: vec![cw_core_v1::msg::ModuleInstantiateInfo { - code_id: proposal_code, - msg: to_json_binary(&cw_proposal_single_v1::msg::InstantiateMsg { - threshold: voting_v1::Threshold::AbsolutePercentage { - percentage: voting_v1::PercentageThreshold::Majority {}, - }, - max_voting_period: cw_utils_v1::Duration::Height(6), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - deposit_info: None, - }) - .unwrap(), - admin: cw_core_v1::msg::Admin::CoreContract {}, - label: "proposal".to_string(), - }], - initial_items: Some(vec![cw_core_v1::msg::InitialItem { - key: "key".to_string(), - value: "value".to_string(), - }]), - }, - &[], - "core", - Some(sender.to_string()), - ) - .unwrap(); - app.execute( - sender.clone(), - WasmMsg::UpdateAdmin { - contract_addr: core.to_string(), - admin: core.to_string(), - } - .into(), - ) - .unwrap(); +// let initial_balances = vec![Cw20Coin { +// address: sender.to_string(), +// amount: Uint128::new(2), +// }]; - // ---- - // stake tokens in the DAO - // ---- +// let core = app +// .instantiate_contract( +// core_code, +// sender.clone(), +// &cw_core_v1::msg::InstantiateMsg { +// admin: Some(sender.to_string()), +// name: "n".to_string(), +// description: "d".to_string(), +// image_url: Some("i".to_string()), +// automatically_add_cw20s: false, +// automatically_add_cw721s: true, +// voting_module_instantiate_info: cw_core_v1::msg::ModuleInstantiateInfo { +// code_id: voting_code, +// msg: to_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { +// active_threshold: None, +// token_info: dao_voting_cw20_staked::msg::TokenInfo::New { +// code_id: cw20_code, +// label: "token".to_string(), +// name: "name".to_string(), +// symbol: "symbol".to_string(), +// decimals: 6, +// initial_balances, +// marketing: None, +// staking_code_id: cw20_stake_code, +// unstaking_duration: None, +// initial_dao_balance: Some(Uint128::new(100)), +// }, +// }) +// .unwrap(), +// admin: cw_core_v1::msg::Admin::CoreContract {}, +// label: "voting".to_string(), +// }, +// proposal_modules_instantiate_info: vec![cw_core_v1::msg::ModuleInstantiateInfo { +// code_id: proposal_code, +// msg: to_binary(&cw_proposal_single_v1::msg::InstantiateMsg { +// threshold: voting_v1::Threshold::AbsolutePercentage { +// percentage: voting_v1::PercentageThreshold::Majority {}, +// }, +// max_voting_period: cw_utils_v1::Duration::Height(6), +// min_voting_period: None, +// only_members_execute: false, +// allow_revoting: false, +// deposit_info: None, +// }) +// .unwrap(), +// admin: cw_core_v1::msg::Admin::CoreContract {}, +// label: "proposal".to_string(), +// }], +// initial_items: Some(vec![cw_core_v1::msg::InitialItem { +// key: "key".to_string(), +// value: "value".to_string(), +// }]), +// }, +// &[], +// "core", +// Some(sender.to_string()), +// ) +// .unwrap(); +// app.execute( +// sender.clone(), +// WasmMsg::UpdateAdmin { +// contract_addr: core.to_string(), +// admin: core.to_string(), +// } +// .into(), +// ) +// .unwrap(); - let token = { - let voting: Addr = app - .wrap() - .query_wasm_smart(&core, &cw_core_v1::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking: Addr = app - .wrap() - .query_wasm_smart( - &voting, - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token: Addr = app - .wrap() - .query_wasm_smart( - &voting, - &dao_voting_cw20_staked::msg::QueryMsg::TokenContract {}, - ) - .unwrap(); - app.execute_contract( - sender.clone(), - token.clone(), - &cw20::Cw20ExecuteMsg::Send { - contract: staking.into_string(), - amount: Uint128::new(1), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - app.update_block(next_block); - token - }; +// // ---- +// // stake tokens in the DAO +// // ---- - // ---- - // create a proposal and add tokens to the treasury. - // ---- +// let token = { +// let voting: Addr = app +// .wrap() +// .query_wasm_smart(&core, &cw_core_v1::msg::QueryMsg::VotingModule {}) +// .unwrap(); +// let staking: Addr = app +// .wrap() +// .query_wasm_smart( +// &voting, +// &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, +// ) +// .unwrap(); +// let token: Addr = app +// .wrap() +// .query_wasm_smart( +// &voting, +// &dao_voting_cw20_staked::msg::QueryMsg::TokenContract {}, +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// token.clone(), +// &cw20::Cw20ExecuteMsg::Send { +// contract: staking.into_string(), +// amount: Uint128::new(1), +// msg: to_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), +// }, +// &[], +// ) +// .unwrap(); +// app.update_block(next_block); +// token +// }; - let proposal = { - let modules: Vec = app - .wrap() - .query_wasm_smart( - &core, - &cw_core_v1::msg::QueryMsg::ProposalModules { - start_at: None, - limit: None, - }, - ) - .unwrap(); - assert!(modules.len() == 1); - modules.into_iter().next().unwrap() - }; +// // ---- +// // create a proposal and add tokens to the treasury. +// // ---- - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Propose { - title: "t".to_string(), - description: "d".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: core.to_string(), - msg: to_json_binary(&cw_core_v1::msg::ExecuteMsg::UpdateCw20List { - to_add: vec![token.to_string()], - to_remove: vec![], - }) - .unwrap(), - funds: vec![], - } - .into()], - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Vote { - proposal_id: 1, - vote: voting_v1::Vote::Yes, - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - let tokens: Vec = app - .wrap() - .query_wasm_smart( - &core, - &cw_core_v1::msg::QueryMsg::Cw20Balances { - start_at: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!( - tokens, - vec![cw_core_v1::query::Cw20BalanceResponse { - addr: token.clone(), - balance: Uint128::new(100), - }] - ); +// let proposal = { +// let modules: Vec = app +// .wrap() +// .query_wasm_smart( +// &core, +// &cw_core_v1::msg::QueryMsg::ProposalModules { +// start_at: None, +// limit: None, +// }, +// ) +// .unwrap(); +// assert!(modules.len() == 1); +// modules.into_iter().next().unwrap() +// }; - // ---- - // Create a proposal that is unexecutable without close_proposal_on_execution_failure - // ---- +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { +// title: "t".to_string(), +// description: "d".to_string(), +// msgs: vec![WasmMsg::Execute { +// contract_addr: core.to_string(), +// msg: to_binary(&cw_core_v1::msg::ExecuteMsg::UpdateCw20List { +// to_add: vec![token.to_string()], +// to_remove: vec![], +// }) +// .unwrap(), +// funds: vec![], +// } +// .into()], +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { +// proposal_id: 1, +// vote: voting_v1::Vote::Yes, +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 1 }, +// &[], +// ) +// .unwrap(); +// let tokens: Vec = app +// .wrap() +// .query_wasm_smart( +// &core, +// &cw_core_v1::msg::QueryMsg::Cw20Balances { +// start_at: None, +// limit: None, +// }, +// ) +// .unwrap(); +// assert_eq!( +// tokens, +// vec![cw_core_v1::query::Cw20BalanceResponse { +// addr: token.clone(), +// balance: Uint128::new(100), +// }] +// ); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Propose { - title: "t".to_string(), - description: "d".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: sender.to_string(), - // more tokens than the DAO posseses. - amount: Uint128::new(101), - }) - .unwrap(), - funds: vec![], - } - .into()], - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Vote { - proposal_id: 2, - vote: voting_v1::Vote::Yes, - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 2 }, - &[], - ) - // can not be executed. - .unwrap_err(); - let cw_proposal_single_v1::query::ProposalResponse { - proposal: cw_proposal_single_v1::proposal::Proposal { status, .. }, - .. - } = app - .wrap() - .query_wasm_smart( - &proposal, - &cw_proposal_single_v1::msg::QueryMsg::Proposal { proposal_id: 2 }, - ) - .unwrap(); - assert_eq!(status, voting_v1::Status::Passed); +// // ---- +// // Create a proposal that is unexecutable without close_proposal_on_execution_failure +// // ---- - // ---- - // create a proposal to migrate to v2 - // ---- +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { +// title: "t".to_string(), +// description: "d".to_string(), +// msgs: vec![WasmMsg::Execute { +// contract_addr: token.to_string(), +// msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { +// recipient: sender.to_string(), +// // more tokens than the DAO posseses. +// amount: Uint128::new(101), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into()], +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { +// proposal_id: 2, +// vote: voting_v1::Vote::Yes, +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 2 }, +// &[], +// ) +// // can not be executed. +// .unwrap_err(); +// let cw_proposal_single_v1::query::ProposalResponse { +// proposal: cw_proposal_single_v1::proposal::Proposal { status, .. }, +// .. +// } = app +// .wrap() +// .query_wasm_smart( +// &proposal, +// &cw_proposal_single_v1::msg::QueryMsg::Proposal { proposal_id: 2 }, +// ) +// .unwrap(); +// assert_eq!(status, voting_v1::Status::Passed); - let v2_core_code = app.store_code(dao_dao_contract()); - let v2_proposal_code = app.store_code(proposal_single_contract()); +// // ---- +// // create a proposal to migrate to v2 +// // ---- - let pre_propose_info = get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }), - false, - ); +// let v2_core_code = app.store_code(dao_dao_contract()); +// let v2_proposal_code = app.store_code(proposal_single_contract()); - // now migrate with valid config - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Propose { - title: "t".to_string(), - description: "d".to_string(), - msgs: vec![ - WasmMsg::Migrate { - contract_addr: core.to_string(), - new_code_id: v2_core_code, - msg: to_json_binary(&dao_interface::msg::MigrateMsg::FromV1 { - dao_uri: Some("dao-uri".to_string()), - params: None, - }) - .unwrap(), - } - .into(), - WasmMsg::Migrate { - contract_addr: proposal.to_string(), - new_code_id: v2_proposal_code, - msg: to_json_binary(&crate::msg::MigrateMsg::FromV1 { - close_proposal_on_execution_failure: true, - pre_propose_info, - veto: Some(VetoConfig { - timelock_duration: Duration::Height(10), - vetoer: sender.to_string(), - early_execute: true, - veto_before_passed: false, - }), - }) - .unwrap(), - } - .into(), - ], - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Vote { - proposal_id: 3, - vote: voting_v1::Vote::Yes, - }, - &[], - ) - .unwrap(); - app.execute_contract( - sender.clone(), - proposal.clone(), - &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 3 }, - &[], - ) - .unwrap(); +// let pre_propose_info = get_pre_propose_info( +// &mut app, +// Some(UncheckedDepositInfo { +// denom: dao_voting::deposit::DepositToken::VotingModuleToken { +// token_type: VotingModuleTokenType::Cw20, +// }, +// amount: Uint128::new(1), +// refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, +// }), +// false, +// ); - // ---- - // execute proposal two. the addition of - // close_proposal_on_execution_failure ought to allow it to close. - // ---- - execute_proposal(&mut app, &proposal, sender.as_str(), 2); - let status = query_proposal(&app, &proposal, 2).proposal.status; - assert_eq!(status, Status::ExecutionFailed); +// // now migrate with valid config +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { +// title: "t".to_string(), +// description: "d".to_string(), +// msgs: vec![ +// WasmMsg::Migrate { +// contract_addr: core.to_string(), +// new_code_id: v2_core_code, +// msg: to_binary(&dao_interface::msg::MigrateMsg::FromV1 { +// dao_uri: Some("dao-uri".to_string()), +// params: None, +// }) +// .unwrap(), +// } +// .into(), +// WasmMsg::Migrate { +// contract_addr: proposal.to_string(), +// new_code_id: v2_proposal_code, +// msg: to_binary(&crate::msg::MigrateMsg::FromV1 { +// close_proposal_on_execution_failure: true, +// pre_propose_info, +// veto: Some(VetoConfig { +// timelock_duration: Duration::Height(10), +// vetoer: sender.to_string(), +// early_execute: true, +// veto_before_passed: false, +// }), +// }) +// .unwrap(), +// } +// .into(), +// ], +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { +// proposal_id: 3, +// vote: voting_v1::Vote::Yes, +// }, +// &[], +// ) +// .unwrap(); +// app.execute_contract( +// sender.clone(), +// proposal.clone(), +// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 3 }, +// &[], +// ) +// .unwrap(); - // ---- - // check that proposal count is still three after proposal state migration. - // ---- - let count = query_proposal_count(&app, &proposal); - assert_eq!(count, 3); +// // ---- +// // execute proposal two. the addition of +// // close_proposal_on_execution_failure ought to allow it to close. +// // ---- +// execute_proposal(&mut app, &proposal, sender.as_str(), 2); +// let status = query_proposal(&app, &proposal, 2).proposal.status; +// assert_eq!(status, Status::ExecutionFailed); - let migrated_existing_props = query_list_proposals(&app, &proposal, None, None); - // assert that even though we migrate with a veto config, - // existing proposals are not affected - for prop in migrated_existing_props.proposals { - assert_eq!(prop.proposal.veto, None); - } - // ---- - // check that proposal module counts have been updated. - // ---- - let module_counts: ProposalModuleCountResponse = app - .wrap() - .query_wasm_smart(&core, &dao_interface::msg::QueryMsg::ProposalModuleCount {}) - .unwrap(); - assert_eq!( - module_counts, - ProposalModuleCountResponse { - active_proposal_module_count: 1, - total_proposal_module_count: 1, - } - ); +// // ---- +// // check that proposal count is still three after proposal state migration. +// // ---- +// let count = query_proposal_count(&app, &proposal); +// assert_eq!(count, 3); - // ---- - // check that items are not overriden in migration. - // ---- - let item: GetItemResponse = app - .wrap() - .query_wasm_smart( - &core, - &dao_interface::msg::QueryMsg::GetItem { - key: "key".to_string(), - }, - ) - .unwrap(); - assert_eq!( - item, - GetItemResponse { - item: Some("value".to_string()) - } - ); +// let migrated_existing_props = query_list_proposals(&app, &proposal, None, None); +// // assert that even though we migrate with a veto config, +// // existing proposals are not affected +// for prop in migrated_existing_props.proposals { +// assert_eq!(prop.proposal.veto, None); +// } +// // ---- +// // check that proposal module counts have been updated. +// // ---- +// let module_counts: ProposalModuleCountResponse = app +// .wrap() +// .query_wasm_smart(&core, &dao_interface::msg::QueryMsg::ProposalModuleCount {}) +// .unwrap(); +// assert_eq!( +// module_counts, +// ProposalModuleCountResponse { +// active_proposal_module_count: 1, +// total_proposal_module_count: 1, +// } +// ); - // ---- - // check that proposal can still be created an executed. - // ---- - make_proposal( - &mut app, - &proposal, - sender.as_str(), - vec![WasmMsg::Execute { - contract_addr: core.to_string(), - msg: to_json_binary(&dao_interface::msg::ExecuteMsg::UpdateCw20List { - to_add: vec![], - to_remove: vec![token.into_string()], - }) - .unwrap(), - funds: vec![], - } - .into()], - ); - vote_on_proposal( - &mut app, - &proposal, - sender.as_str(), - 4, - dao_voting::voting::Vote::Yes, - ); +// // ---- +// // check that items are not overriden in migration. +// // ---- +// let item: GetItemResponse = app +// .wrap() +// .query_wasm_smart( +// &core, +// &dao_interface::msg::QueryMsg::GetItem { +// key: "key".to_string(), +// }, +// ) +// .unwrap(); +// assert_eq!( +// item, +// GetItemResponse { +// item: Some("value".to_string()) +// } +// ); - let new_prop = query_proposal(&app, &proposal, 4); - assert_eq!( - new_prop.proposal.veto, - Some(VetoConfig { - timelock_duration: Duration::Height(10), - vetoer: sender.to_string(), - early_execute: true, - veto_before_passed: false, - }) - ); +// // ---- +// // check that proposal can still be created an executed. +// // ---- +// make_proposal( +// &mut app, +// &proposal, +// sender.as_str(), +// vec![WasmMsg::Execute { +// contract_addr: core.to_string(), +// msg: to_binary(&dao_interface::msg::ExecuteMsg::UpdateCw20List { +// to_add: vec![], +// to_remove: vec![token.into_string()], +// }) +// .unwrap(), +// funds: vec![], +// } +// .into()], +// ); +// vote_on_proposal( +// &mut app, +// &proposal, +// sender.as_str(), +// 4, +// dao_voting::voting::Vote::Yes, +// ); - execute_proposal(&mut app, &proposal, sender.as_str(), 4); - let tokens: Vec = app - .wrap() - .query_wasm_smart( - &core, - &dao_interface::msg::QueryMsg::Cw20Balances { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert!(tokens.is_empty()) -} +// let new_prop = query_proposal(&app, &proposal, 4); +// assert_eq!( +// new_prop.proposal.veto, +// Some(VetoConfig { +// timelock_duration: Duration::Height(10), +// vetoer: sender.to_string(), +// early_execute: true, +// veto_before_passed: false, +// }) +// ); + +// execute_proposal(&mut app, &proposal, sender.as_str(), 4); +// let tokens: Vec = app +// .wrap() +// .query_wasm_smart( +// &core, +// &dao_interface::msg::QueryMsg::Cw20Balances { +// start_after: None, +// limit: None, +// }, +// ) +// .unwrap(); +// assert!(tokens.is_empty()) +// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/mod.rs b/contracts/proposal/dao-proposal-single/src/testing/mod.rs index 12c9b9a..9fae24e 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/mod.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/mod.rs @@ -8,3 +8,4 @@ mod queries; mod tests; pub(crate) const CREATOR_ADDR: &str = "creator"; +pub(crate) const DAO_ADDR: &str = "dao"; diff --git a/contracts/proposal/dao-proposal-single/src/testing/queries.rs b/contracts/proposal/dao-proposal-single/src/testing/queries.rs index a246f1c..2d2f383 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/queries.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/queries.rs @@ -1,214 +1,18 @@ -use cosmwasm_std::{Addr, Uint128}; -use cw_multi_test::App; -use dao_interface::state::{ProposalModule, ProposalModuleStatus}; +use cosmwasm_std::Addr; +use secret_multi_test::App; -use cw_hooks::HooksResponse; -use dao_pre_propose_single as cppbps; -use dao_voting::pre_propose::ProposalCreationPolicy; +use crate::msg::QueryMsg; -use crate::{ - msg::QueryMsg, - query::{ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse}, - state::Config, -}; - -pub(crate) fn query_deposit_config_and_pre_propose_module( - app: &App, - proposal_single: &Addr, -) -> (cppbps::Config, Addr) { - let proposal_creation_policy = query_creation_policy(app, proposal_single); - - if let ProposalCreationPolicy::Module { addr: module_addr } = proposal_creation_policy { - let deposit_config = query_pre_proposal_single_config(app, &module_addr); - - (deposit_config, module_addr) - } else { - panic!("no pre-propose module.") - } -} - -pub(crate) fn query_proposal_config(app: &App, proposal_single: &Addr) -> Config { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::Config {}) - .unwrap() -} - -pub(crate) fn query_creation_policy(app: &App, proposal_single: &Addr) -> ProposalCreationPolicy { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::ProposalCreationPolicy {}) - .unwrap() -} - -pub(crate) fn query_list_proposals( - app: &App, - proposal_single: &Addr, - start_after: Option, - limit: Option, -) -> ProposalListResponse { - app.wrap() - .query_wasm_smart( - proposal_single, - &QueryMsg::ListProposals { start_after, limit }, - ) - .unwrap() -} - -pub(crate) fn query_list_votes( +pub(crate) fn query_next_proposal_id( app: &App, proposal_single: &Addr, - proposal_id: u64, - start_after: Option, - limit: Option, -) -> VoteListResponse { + proposal_single_code_hash: String, +) -> u64 { app.wrap() .query_wasm_smart( + proposal_single_code_hash, proposal_single, - &QueryMsg::ListVotes { - proposal_id, - start_after, - limit, - }, - ) - .unwrap() -} - -pub(crate) fn query_vote( - app: &App, - proposal_module: &Addr, - who: &str, - proposal_id: u64, -) -> VoteResponse { - app.wrap() - .query_wasm_smart( - proposal_module, - &QueryMsg::GetVote { - proposal_id, - voter: who.to_string(), - }, + &QueryMsg::NextProposalId {}, ) .unwrap() } - -pub(crate) fn query_proposal_hooks(app: &App, proposal_single: &Addr) -> HooksResponse { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::ProposalHooks {}) - .unwrap() -} - -pub(crate) fn query_vote_hooks(app: &App, proposal_single: &Addr) -> HooksResponse { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::VoteHooks {}) - .unwrap() -} - -pub(crate) fn query_list_proposals_reverse( - app: &App, - proposal_single: &Addr, - start_before: Option, - limit: Option, -) -> ProposalListResponse { - app.wrap() - .query_wasm_smart( - proposal_single, - &QueryMsg::ReverseProposals { - start_before, - limit, - }, - ) - .unwrap() -} - -pub(crate) fn query_pre_proposal_single_config(app: &App, pre_propose: &Addr) -> cppbps::Config { - app.wrap() - .query_wasm_smart(pre_propose, &cppbps::QueryMsg::Config {}) - .unwrap() -} - -pub(crate) fn query_pre_proposal_single_deposit_info( - app: &App, - pre_propose: &Addr, - proposal_id: u64, -) -> cppbps::DepositInfoResponse { - app.wrap() - .query_wasm_smart(pre_propose, &cppbps::QueryMsg::DepositInfo { proposal_id }) - .unwrap() -} - -pub(crate) fn query_single_proposal_module(app: &App, core_addr: &Addr) -> Addr { - let modules: Vec = app - .wrap() - .query_wasm_smart( - core_addr, - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - // Filter out disabled modules. - let modules = modules - .into_iter() - .filter(|module| module.status == ProposalModuleStatus::Enabled) - .collect::>(); - - assert_eq!( - modules.len(), - 1, - "wrong proposal module count. expected 1, got {}", - modules.len() - ); - - modules.into_iter().next().unwrap().address -} - -pub(crate) fn query_dao_token(app: &App, core_addr: &Addr) -> Addr { - let voting_module = query_voting_module(app, core_addr); - app.wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap() -} - -pub(crate) fn query_voting_module(app: &App, core_addr: &Addr) -> Addr { - app.wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap() -} - -pub(crate) fn query_balance_cw20, U: Into>( - app: &App, - contract_addr: T, - address: U, -) -> Uint128 { - let msg = cw20::Cw20QueryMsg::Balance { - address: address.into(), - }; - let result: cw20::BalanceResponse = app.wrap().query_wasm_smart(contract_addr, &msg).unwrap(); - result.balance -} - -pub(crate) fn query_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { - let res = app.wrap().query_balance(who, denom).unwrap(); - res.amount -} - -pub(crate) fn query_proposal(app: &App, proposal_single: &Addr, id: u64) -> ProposalResponse { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: id }) - .unwrap() -} - -pub(crate) fn query_next_proposal_id(app: &App, proposal_single: &Addr) -> u64 { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::NextProposalId {}) - .unwrap() -} - -pub(crate) fn query_proposal_count(app: &App, proposal_single: &Addr) -> u64 { - app.wrap() - .query_wasm_smart(proposal_single, &QueryMsg::ProposalCount {}) - .unwrap() -} diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index 404dc04..cc37a14 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -1,4089 +1,471 @@ -use std::ops::Add; - -use cosmwasm_std::{ - coins, - testing::{mock_dependencies, mock_env}, - to_json_binary, Addr, Attribute, BankMsg, Binary, ContractInfoResponse, CosmosMsg, Decimal, - Empty, Reply, StdError, SubMsgResult, Uint128, WasmMsg, WasmQuery, -}; -use cw2::ContractVersion; -use cw20::Cw20Coin; -use cw_denom::CheckedDenom; -use cw_hooks::{HookError, HooksResponse}; -use cw_multi_test::{next_block, App, Executor}; -use cw_utils::Duration; -use dao_interface::{ - state::{Admin, ModuleInstantiateInfo}, - voting::InfoResponse, -}; -use dao_testing::{ShouldExecute, TestSingleChoiceVote}; -use dao_voting::{ - deposit::{CheckedDepositInfo, UncheckedDepositInfo, VotingModuleTokenType}, - pre_propose::{PreProposeInfo, ProposalCreationPolicy}, - proposal::{SingleChoiceProposeMsg as ProposeMsg, MAX_PROPOSAL_SIZE}, - reply::{ - failed_pre_propose_module_hook_id, mask_proposal_execution_proposal_id, - mask_proposal_hook_index, mask_vote_hook_index, - }, - status::Status, - threshold::{ActiveThreshold, PercentageThreshold, Threshold}, - veto::{VetoConfig, VetoError}, - voting::{Vote, Votes}, -}; - -use crate::{ - contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, - proposal::SingleChoiceProposal, - query::{ProposalResponse, VoteInfo}, - state::Config, - testing::{ - contracts::{pre_propose_single_contract, proposal_single_contract}, - execute::{ - add_proposal_hook, add_proposal_hook_should_fail, add_vote_hook, - add_vote_hook_should_fail, close_proposal, close_proposal_should_fail, - execute_proposal, execute_proposal_should_fail, instantiate_cw20_base_default, - make_proposal, mint_cw20s, mint_natives, remove_proposal_hook, - remove_proposal_hook_should_fail, remove_vote_hook, remove_vote_hook_should_fail, - update_rationale, vote_on_proposal, vote_on_proposal_should_fail, - }, - instantiate::{ - get_default_non_token_dao_proposal_module_instantiate, - get_default_token_dao_proposal_module_instantiate, get_pre_propose_info, - instantiate_with_cw4_groups_governance, instantiate_with_staked_balances_governance, - instantiate_with_staking_active_threshold, - }, - queries::{ - query_balance_cw20, query_balance_native, query_creation_policy, query_dao_token, - query_deposit_config_and_pre_propose_module, query_list_proposals, - query_list_proposals_reverse, query_list_votes, query_pre_proposal_single_config, - query_pre_proposal_single_deposit_info, query_proposal, query_proposal_config, - query_proposal_hooks, query_single_proposal_module, query_vote_hooks, - query_voting_module, - }, +use cosmwasm_std::{testing::mock_info, Addr, ContractInfo}; +use dao_voting::voting::Vote; +use secret_multi_test::{App, Executor}; +use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; + +use crate::testing::{ + contracts::proposal_single_contract, + execute::{ + add_proposal_hook, add_proposal_hook_should_fail, add_vote_hook, add_vote_hook_should_fail, + close_proposal_should_fail, execute_proposal_should_fail, make_proposal, + remove_proposal_hook, remove_proposal_hook_should_fail, remove_vote_hook, + remove_vote_hook_should_fail, update_rationale, vote_on_proposal_should_fail, }, - ContractError, + instantiate::get_default_token_dao_proposal_module_instantiate, }; use super::{ - do_votes::do_votes_staked_balances, - execute::vote_on_proposal_with_rationale, - queries::{query_next_proposal_id, query_vote}, - CREATOR_ADDR, + execute::{ + create_viewing_key, execute_veto_fails, update_config, update_config_should_fail, + update_pre_propose_info, update_pre_propose_info_should_fail, + }, + instantiate::instantiate_query_auth, + CREATOR_ADDR, DAO_ADDR, }; struct CommonTest { app: App, - core_addr: Addr, - proposal_module: Addr, - gov_token: Addr, - proposal_id: u64, + proposal_single_contract_info: ContractInfo, } -fn setup_test(messages: Vec) -> CommonTest { +fn setup_test(sender: &str) -> CommonTest { let mut app = App::default(); - let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - // Mint some tokens to pay the proposal deposit. - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages); + let proposal_module_contract_info = app.store_code(proposal_single_contract()); + let query_auth = instantiate_query_auth(&mut app); + let instantiate = get_default_token_dao_proposal_module_instantiate( + RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, + "dao_dao_code_hash".to_string(), + ); + let proposal_single_contract_info = app + .instantiate_contract( + proposal_module_contract_info, + Addr::unchecked(sender), + &instantiate, + &[], + "proposal_single", + None, + ) + .unwrap(); CommonTest { app, - core_addr, - proposal_module, - gov_token, - proposal_id, + proposal_single_contract_info, } } #[test] -fn test_simple_propose_staked_balances() { +fn test_simple_instantiate_proposal() { let CommonTest { - app, - core_addr: _, - proposal_module, - gov_token, - proposal_id, - } = setup_test(vec![]); - - let created = query_proposal(&app, &proposal_module, proposal_id); - let current_block = app.block_info(); - - // These values just come from the default instantiate message - // values. - let expected = SingleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: Duration::Time(604800).after(¤t_block), - min_voting_period: None, - threshold: Threshold::ThresholdQuorum { - quorum: PercentageThreshold::Percent(Decimal::percent(15)), - threshold: PercentageThreshold::Majority {}, - }, - allow_revoting: false, - total_power: Uint128::new(100_000_000), - msgs: vec![], - status: Status::Open, - veto: None, - votes: Votes::zero(), - }; - - assert_eq!(created.proposal, expected); - assert_eq!(created.id, 1u64); - - // Check that the deposit info for this proposal looks right. - let (_, pre_propose) = query_deposit_config_and_pre_propose_module(&app, &proposal_module); - let deposit_response = query_pre_proposal_single_deposit_info(&app, &pre_propose, proposal_id); - - assert_eq!(deposit_response.proposer, Addr::unchecked(CREATOR_ADDR)); - assert_eq!( - deposit_response.deposit_info, - Some(CheckedDepositInfo { - denom: cw_denom::CheckedDenom::Cw20(gov_token), - amount: Uint128::new(10_000_000), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed - }) - ); + app: _, + proposal_single_contract_info: _, + } = setup_test(DAO_ADDR); } +// this will fail currently as custom function parse_reply_get_contract_address from event is not woring +// for testing and we can't create a normal dao due to it. #[test] -fn test_simple_proposal_cw4_voting() { - let mut app = App::default(); - let instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); - let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); +#[should_panic( + expected = "Generic error: Querier contract error: secret_multi_test::multi::wasm::ContractData not found" +)] +fn test_create_proposal_without_voting_module_will_fail() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - let created = query_proposal(&app, &proposal_module, id); - let current_block = app.block_info(); + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); - // These values just come from the default instantiate message - // values. - let expected = SingleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: Duration::Time(604800).after(¤t_block), - min_voting_period: None, - threshold: Threshold::ThresholdQuorum { - threshold: PercentageThreshold::Percent(Decimal::percent(15)), - quorum: PercentageThreshold::Majority {}, + // Create Proposal + make_proposal( + &mut app, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), }, - allow_revoting: false, - total_power: Uint128::new(1), - msgs: vec![], - status: Status::Open, - veto: None, - votes: Votes::zero(), - }; - - assert_eq!(created.proposal, expected); - assert_eq!(created.id, 1u64); - - // Check that the deposit info for this proposal looks right. - let (_, pre_propose) = query_deposit_config_and_pre_propose_module(&app, &proposal_module); - let deposit_response = query_pre_proposal_single_deposit_info(&app, &pre_propose, id); - - assert_eq!(deposit_response.proposer, Addr::unchecked(CREATOR_ADDR)); - assert_eq!(deposit_response.deposit_info, None,); -} - -#[test] -fn test_propose_supports_stargate_messages() { - // If we can make a proposal with a stargate message, we support - // stargate messages in proposals. - setup_test(vec![CosmosMsg::Stargate { - type_url: "foo_type".to_string(), - value: Binary::default(), - }]); + vec![], + ); } -/// Test that the deposit token is properly set to the voting module -/// token during instantiation. #[test] -fn test_voting_module_token_instantiate() { +fn test_vote_on_proposal_with_invalid_proposal_id_will_fail() { let CommonTest { - app, - core_addr: _, - proposal_module, - gov_token, - proposal_id, - } = setup_test(vec![]); - - let (_, pre_propose) = query_deposit_config_and_pre_propose_module(&app, &proposal_module); - let deposit_response = query_pre_proposal_single_deposit_info(&app, &pre_propose, proposal_id); + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - let deposit_token = if let Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(addr), - .. - }) = deposit_response.deposit_info - { - addr - } else { - panic!("voting module should have governance token") - }; - assert_eq!(deposit_token, gov_token) -} + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); -#[test] -#[should_panic( - expected = "Error parsing into type dao_voting_cw4::msg::QueryMsg: unknown variant `token_contract`" -)] -fn test_deposit_token_voting_module_token_fails_if_no_voting_module_token() { - let mut app = App::default(); - let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate_with_cw4_groups_governance(&mut app, instantiate, None); + // vote on Proposal will fail + vote_on_proposal_should_fail( + &mut app, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), + }, + 1, + Vote::Yes, + ); } #[test] -fn test_instantiate_with_non_voting_module_cw20_deposit() { - let mut app = App::default(); - let alt_cw20 = instantiate_cw20_base_default(&mut app); +#[should_panic(expected = "no vote exists for proposal (1) and voter (creator)")] +fn test_update_rational_fails_for_no_proposal() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - let mut instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); - // hehehehehehehehe - instantiate.pre_propose_info = get_pre_propose_info( + // update rational fais for no proposal + update_rationale( &mut app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::Token { - denom: cw_denom::UncheckedDenom::Cw20(alt_cw20.to_string()), - }, - amount: Uint128::new(10_000_000), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }), - false, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + CREATOR_ADDR, + 1, + Some("new_rational".into()), ); +} - let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); +#[test] +fn execute_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - let created = query_proposal(&app, &proposal_module, proposal_id); - let current_block = app.block_info(); + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); - // These values just come from the default instantiate message - // values. - let expected = SingleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: Duration::Time(604800).after(¤t_block), - min_voting_period: None, - threshold: Threshold::ThresholdQuorum { - threshold: PercentageThreshold::Percent(Decimal::percent(15)), - quorum: PercentageThreshold::Majority {}, + // execute on Proposal will fail + execute_proposal_should_fail( + &mut app, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), }, - allow_revoting: false, - total_power: Uint128::new(1), - msgs: vec![], - status: Status::Open, - votes: Votes::zero(), - veto: None, - }; - - assert_eq!(created.proposal, expected); - assert_eq!(created.id, 1u64); - - // Check that the deposit info for this proposal looks right. - let (_, pre_propose) = query_deposit_config_and_pre_propose_module(&app, &proposal_module); - let deposit_response = query_pre_proposal_single_deposit_info(&app, &pre_propose, proposal_id); - - assert_eq!(deposit_response.proposer, Addr::unchecked(CREATOR_ADDR)); - assert_eq!( - deposit_response.deposit_info, - Some(CheckedDepositInfo { - denom: cw_denom::CheckedDenom::Cw20(alt_cw20), - amount: Uint128::new(10_000_000), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed - }) + 1, ); } #[test] -fn test_proposal_message_execution() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn execute_veto_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // veto on Proposal will fail + execute_veto_fails( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + 1, ); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::zero()); - assert_eq!(native_balance, Uint128::zero()); +} + +#[test] +fn close_proposal_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - vote_on_proposal( + // close on Proposal will fail + close_proposal_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - proposal_id, - Vote::Yes, + 1, ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - - // Can't use library function because we expect this to fail due - // to insufficent balance in the bank module. - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err(); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::new(20_000_000)); - assert_eq!(native_balance, Uint128::new(10)); - - // Sneak in a check here that proposals can't be executed more - // than once in the on close on execute config suituation. - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})) } #[test] -fn test_proposal_message_timelock_execution() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: false, - }; - instantiate.close_proposal_on_execution_failure = false; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "oversight".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn update_config_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::zero()); - assert_eq!(native_balance, Uint128::zero()); + let query_auth = instantiate_query_auth(&mut app); - vote_on_proposal( + // update config + update_config( &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, ); - - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - // vetoer can't execute when timelock is active and - // early execute not enabled. - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::VetoError(VetoError::NoEarlyExecute {})); - - // Proposal cannot be excuted before timelock expires - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::Timelocked {})); - - // Time passes - app.update_block(|block| { - block.time = block.time.plus_seconds(604800 + 200); - }); - - // Proposal executes successfully - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - - Ok(()) } -// only the authorized vetoer can veto an open proposal #[test] -fn test_open_proposal_veto_unauthorized() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: true, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn update_config_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + let query_auth = instantiate_query_auth(&mut app); + + // update config fails + update_config_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, ); - - // only the vetoer can veto - let err: ContractError = app - .execute_contract( - Addr::unchecked("not-oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {})); } -// open proposal can only be vetoed if `veto_before_passed` flag is enabled #[test] -fn test_open_proposal_veto_with_early_veto_flag_disabled() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn update_pre_propose_info_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // update pre-propose + update_pre_propose_info( &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!( - err, - ContractError::VetoError(VetoError::NoVetoBeforePassed {}) + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, ); } #[test] -fn test_open_proposal_veto_with_no_timelock() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - instantiate.veto = None; - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn update_pre_propose_info_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // update pre-propose fails + update_pre_propose_info_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!( - err, - ContractError::VetoError(VetoError::NoVetoConfiguration {}) ); } -// if proposal is not open or timelocked, attempts to veto should -// throw an error #[test] -fn test_vetoed_proposal_veto() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: true, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn add_proposal_hook_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // add proposal hook + add_proposal_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - - app.execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Vetoed {}); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!( - ContractError::VetoError(VetoError::InvalidProposalStatus { - status: "vetoed".to_string() - }), - err, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); } #[test] -fn test_open_proposal_veto_early() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: true, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn add_proposal_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // add proposal hook fails + add_proposal_hook_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + "hook_addr", + "hook_code_hash", ); - - app.execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Vetoed {}); } -// only the vetoer can veto during timelock period #[test] -fn test_timelocked_proposal_veto_unauthorized() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "oversight".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn remove_proposal_hook_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // add proposal hook + add_proposal_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + &proposal_single_contract_info.address, + proposal_single_contract_info.clone().code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); - vote_on_proposal( + // remove proposal hook + remove_proposal_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } - ); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("not-oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {}),); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); - - Ok(()) } -// vetoer can only veto the proposal before the timelock expires #[test] -fn test_timelocked_proposal_veto_expired_timelock() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "oversight".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn remove_proposal_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // remove proposal hook fails + remove_proposal_hook_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + "hook_addr", + "hook_code_hash", ); +} - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); +#[test] +fn remove_proposal_hook_fails_for_no_proposal_hook() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } + // remove proposal hook fails + remove_proposal_hook_should_fail( + &mut app, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); - app.update_block(|b| b.time = b.time.plus_seconds(604800 + 200)); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {}),); - - Ok(()) } -// vetoer can only exec timelocked prop if the early exec flag is enabled #[test] -fn test_timelocked_proposal_execute_no_early_exec() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn add_vote_hook_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // add vote hook + add_vote_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); +} + +#[test] +fn add_vote_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - vote_on_proposal( + // add vote hook fails + add_vote_hook_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } + "vote_hook_addr", + "vote_hook_code_hash", ); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::NoEarlyExecute {}),); - - Ok(()) } #[test] -fn test_timelocked_proposal_execute_early() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn remove_vote_hook_works() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // add vote hook + add_vote_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + &proposal_single_contract_info.address, + proposal_single_contract_info.clone().code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - vote_on_proposal( + // remove vote hook + remove_vote_hook( &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } - ); - - // assert timelock is active - assert!(!veto_config - .timelock_duration - .after(&app.block_info()) - .is_expired(&app.block_info())); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - app.execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed {}); - - Ok(()) } -// only vetoer can exec timelocked prop early #[test] -fn test_timelocked_proposal_execute_active_timelock_unauthorized() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); +fn remove_vote_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( + // remove vote hook fails + remove_vote_hook_should_fail( &mut app, - &proposal_module, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], + "vote_hook_addr", + "vote_hook_code_hash", ); +} - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); +#[test] +fn remove_vote_hook_fails_for_no_proposal_hook() { + let CommonTest { + mut app, + proposal_single_contract_info, + } = setup_test(DAO_ADDR); - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } + // remove vote hook fails + remove_vote_hook_should_fail( + &mut app, + &proposal_single_contract_info.address, + proposal_single_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - - // assert timelock is active - assert!(!veto_config - .timelock_duration - .after(&app.block_info()) - .is_expired(&app.block_info())); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::Timelocked {}),); - - Ok(()) -} - -// anyone can exec the prop after the timelock expires -#[test] -fn test_timelocked_proposal_execute_expired_timelock_not_vetoer() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - let expiration = proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?; - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { expiration } - ); - - app.update_block(|b| b.time = b.time.plus_seconds(604800 + 201)); - // assert timelock is expired - assert!(expiration.is_expired(&app.block_info())); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed {},); - - Ok(()) -} - -#[test] -fn test_proposal_message_timelock_veto() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::zero()); - assert_eq!(native_balance, Uint128::zero()); - - // Vetoer can't veto early - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!( - err, - ContractError::VetoError(VetoError::NoVetoBeforePassed {}) - ); - - // Vote on proposal to pass it - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } - ); - - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - // Non-vetoer cannot veto - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::VetoError(VetoError::Unauthorized {})); - - // Oversite vetos prop - app.execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Vetoed); - - Ok(()) -} - -#[test] -fn test_proposal_message_timelock_early_execution() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "oversight".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::zero()); - assert_eq!(native_balance, Uint128::zero()); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?, - } - ); - - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - // Proposal can be executed early by vetoer - execute_proposal(&mut app, &proposal_module, "oversight", proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - - Ok(()) -} - -#[test] -fn test_proposal_message_timelock_veto_before_passed() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - instantiate.veto = Some(VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: false, - veto_before_passed: true, - }); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "oversight".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is open for voting - assert_eq!(proposal.proposal.status, Status::Open); - - // Oversite vetos prop - app.execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id }, - &[], - ) - .unwrap(); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Vetoed); - - // mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - // // Proposal can be executed early by vetoer - // execute_proposal(&mut app, &proposal_module, "oversight", proposal_id); - // let proposal = query_proposal(&app, &proposal_module, proposal_id); - // assert_eq!(proposal.proposal.status, Status::Executed); -} - -#[test] -fn test_veto_only_members_execute_proposal() -> anyhow::Result<()> { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.close_proposal_on_execution_failure = false; - let veto_config = VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: "oversight".to_string(), - early_execute: true, - veto_before_passed: false, - }; - instantiate.veto = Some(veto_config.clone()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![ - WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }) - .unwrap(), - funds: vec![], - } - .into(), - BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into(), - ], - ); - let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(cw20_balance, Uint128::zero()); - assert_eq!(native_balance, Uint128::zero()); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - - // Proposal is timelocked to the moment of prop expiring + timelock delay - let expiration = proposal - .proposal - .expiration - .add(veto_config.timelock_duration)?; - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { expiration } - ); - - app.update_block(|b| b.time = b.time.plus_seconds(604800 + 101)); - // assert timelock is expired - assert!(expiration.is_expired(&app.block_info())); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - - // Proposal cannot be executed by vetoer once timelock expired - let err: ContractError = app - .execute_contract( - Addr::unchecked("oversight"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - - // Proposal can be executed by member once timelock expired - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Executed); - - Ok(()) -} - -#[test] -fn test_proposal_close_after_expiry() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id, - } = setup_test(vec![BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into()]); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - - // Try and close the proposal. This shoudl fail as the proposal is - // open. - let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::WrongCloseStatus {})); - - // Expire the proposal. Now it should be closable. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - close_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Closed); -} - -#[test] -fn test_proposal_cant_close_after_expiry_is_passed() { - let mut app = App::default(); - let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "quorum".to_string(), - amount: Uint128::new(15), - }, - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(85), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - mint_natives(&mut app, core_addr.as_str(), coins(10, "ujuno")); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into()], - ); - vote_on_proposal(&mut app, &proposal_module, "quorum", proposal_id, Vote::Yes); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Open); - - // Expire the proposal. This should pass it. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed,); - - // Make sure it can't be closed. - let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::WrongCloseStatus {})); - - // Executed proposals may not be closed. - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::WrongCloseStatus {})); - let balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); - assert_eq!(balance, Uint128::new(10)); - let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::WrongCloseStatus {})); -} - -#[test] -fn test_execute_no_non_passed_execution() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token, - proposal_id, - } = setup_test(vec![BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into()]); - mint_natives(&mut app, core_addr.as_str(), coins(100, "ujuno")); - - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})); - - // Expire the proposal. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - // Can't execute more than once. - let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::NotPassed {})); -} - -#[test] -fn test_cant_execute_not_member_when_proposal_created() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token, - proposal_id, - } = setup_test(vec![BankMsg::Send { - to_address: CREATOR_ADDR.to_string(), - amount: coins(10, "ujuno"), - } - .into()]); - mint_natives(&mut app, core_addr.as_str(), coins(100, "ujuno")); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - - // Give noah some tokens. - mint_cw20s(&mut app, &gov_token, &core_addr, "noah", 20_000_000); - // Have noah stake some. - let voting_module = query_voting_module(&app, &core_addr); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - app.execute_contract( - Addr::unchecked("noah"), - gov_token, - &cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(10_000_000), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - // Update the block so that the staked balance appears. - app.update_block(|block| block.height += 1); - - // Can't execute from member who wasn't a member when the proposal was - // created. - let err = execute_proposal_should_fail(&mut app, &proposal_module, "noah", proposal_id); - assert!(matches!(err, ContractError::Unauthorized {})); -} - -#[test] -fn test_update_config() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id, - } = setup_test(vec![]); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - // Make a proposal to update the config. - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![WasmMsg::Execute { - contract_addr: proposal_module.to_string(), - msg: to_json_binary(&ExecuteMsg::UpdateConfig { - veto: Some(VetoConfig { - timelock_duration: Duration::Height(2), - vetoer: CREATOR_ADDR.to_string(), - early_execute: false, - veto_before_passed: false, - }), - threshold: Threshold::AbsoluteCount { - threshold: Uint128::new(10_000), - }, - max_voting_period: Duration::Height(6), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - dao: core_addr.to_string(), - close_proposal_on_execution_failure: false, - }) - .unwrap(), - funds: vec![], - } - .into()], - ); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - let config = query_proposal_config(&app, &proposal_module); - assert_eq!( - config, - Config { - veto: Some(VetoConfig { - timelock_duration: Duration::Height(2), - vetoer: CREATOR_ADDR.to_string(), - early_execute: false, - veto_before_passed: false, - }), - threshold: Threshold::AbsoluteCount { - threshold: Uint128::new(10_000) - }, - max_voting_period: Duration::Height(6), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - dao: core_addr.clone(), - close_proposal_on_execution_failure: false, - } - ); - - // Check that non-dao address may not update config. - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &&ExecuteMsg::UpdateConfig { - veto: None, - threshold: Threshold::AbsoluteCount { - threshold: Uint128::new(10_000), - }, - max_voting_period: Duration::Height(6), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - dao: core_addr.to_string(), - close_proposal_on_execution_failure: false, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})); - - // Check that veto config is validated (mismatching duration units). - let err: ContractError = app - .execute_contract( - Addr::unchecked(core_addr.clone()), - proposal_module, - &&ExecuteMsg::UpdateConfig { - veto: Some(VetoConfig { - timelock_duration: Duration::Time(100), - vetoer: CREATOR_ADDR.to_string(), - early_execute: false, - veto_before_passed: false, - }), - threshold: Threshold::AbsoluteCount { - threshold: Uint128::new(10_000), - }, - max_voting_period: Duration::Height(6), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - dao: core_addr.to_string(), - close_proposal_on_execution_failure: false, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!( - err, - ContractError::VetoError(VetoError::TimelockDurationUnitMismatch {}) - )) -} - -#[test] -fn test_anyone_may_propose_and_proposal_listing() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - for addr in 'm'..'z' { - let addr = addr.to_string().repeat(6); - let proposal_id = make_proposal(&mut app, &proposal_module, &addr, vec![]); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - // Only members can execute still. - let err = execute_proposal_should_fail(&mut app, &proposal_module, &addr, proposal_id); - assert!(matches!(err, ContractError::Unauthorized {})); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - } - - // Now that we've got all these proposals sitting around, lets - // test that we can query them. - - let proposals_forward = query_list_proposals(&app, &proposal_module, None, None); - let mut proposals_reverse = query_list_proposals_reverse(&app, &proposal_module, None, None); - proposals_reverse.proposals.reverse(); - assert_eq!(proposals_reverse, proposals_forward); - - // Check the proposers and (implicitly) the ordering. - for (index, addr) in ('m'..'z').enumerate() { - let addr = addr.to_string().repeat(6); - assert_eq!( - proposals_forward.proposals[index].proposal.proposer, - Addr::unchecked(addr) - ) - } - - let four_and_five = query_list_proposals(&app, &proposal_module, Some(3), Some(2)); - let mut five_and_four = query_list_proposals_reverse(&app, &proposal_module, Some(6), Some(2)); - five_and_four.proposals.reverse(); - - assert_eq!(five_and_four, four_and_five); - assert_eq!( - four_and_five.proposals[0].proposal.proposer, - Addr::unchecked("pppppp") - ); - - let current_block = app.block_info(); - assert_eq!( - four_and_five.proposals[0], - ProposalResponse { - id: 4, - proposal: SingleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked("pppppp"), - start_height: current_block.height, - min_voting_period: None, - expiration: Duration::Time(604800).after(¤t_block), - threshold: Threshold::ThresholdQuorum { - quorum: PercentageThreshold::Percent(Decimal::percent(15)), - threshold: PercentageThreshold::Majority {}, - }, - allow_revoting: false, - total_power: Uint128::new(100_000_000), - msgs: vec![], - status: Status::Executed, - votes: Votes { - yes: Uint128::new(100_000_000), - no: Uint128::zero(), - abstain: Uint128::zero() - }, - veto: None - } - } - ) -} - -#[test] -fn test_proposal_hook_registration() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - - let proposal_hooks = query_proposal_hooks(&app, &proposal_module); - assert_eq!( - proposal_hooks.hooks.len(), - 0, - "pre-propose deposit module should not show on this listing" - ); - - // non-dao may not add a hook. - let err = - add_proposal_hook_should_fail(&mut app, &proposal_module, CREATOR_ADDR, "proposalhook"); - assert!(matches!(err, ContractError::Unauthorized {})); - - add_proposal_hook( - &mut app, - &proposal_module, - core_addr.as_str(), - "proposalhook", - ); - let err = add_proposal_hook_should_fail( - &mut app, - &proposal_module, - core_addr.as_str(), - "proposalhook", - ); - assert!(matches!( - err, - ContractError::HookError(HookError::HookAlreadyRegistered {}) - )); - - let proposal_hooks = query_proposal_hooks(&app, &proposal_module); - assert_eq!(proposal_hooks.hooks[0], "proposalhook".to_string()); - - // Only DAO can remove proposal hooks. - let err = - remove_proposal_hook_should_fail(&mut app, &proposal_module, CREATOR_ADDR, "proposalhook"); - assert!(matches!(err, ContractError::Unauthorized {})); - remove_proposal_hook( - &mut app, - &proposal_module, - core_addr.as_str(), - "proposalhook", - ); - let proposal_hooks = query_proposal_hooks(&app, &proposal_module); - assert_eq!(proposal_hooks.hooks.len(), 0); - - // Can not remove that which does not exist. - let err = remove_proposal_hook_should_fail( - &mut app, - &proposal_module, - core_addr.as_str(), - "proposalhook", - ); - assert!(matches!( - err, - ContractError::HookError(HookError::HookNotRegistered {}) - )); -} - -#[test] -fn test_vote_hook_registration() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - - let vote_hooks = query_vote_hooks(&app, &proposal_module); - assert!(vote_hooks.hooks.is_empty(),); - - // non-dao may not add a hook. - let err = add_vote_hook_should_fail(&mut app, &proposal_module, CREATOR_ADDR, "votehook"); - assert!(matches!(err, ContractError::Unauthorized {})); - - add_vote_hook(&mut app, &proposal_module, core_addr.as_str(), "votehook"); - - let vote_hooks = query_vote_hooks(&app, &proposal_module); - assert_eq!( - vote_hooks, - HooksResponse { - hooks: vec!["votehook".to_string()] - } - ); - - let err = add_vote_hook_should_fail(&mut app, &proposal_module, core_addr.as_str(), "votehook"); - assert!(matches!( - err, - ContractError::HookError(HookError::HookAlreadyRegistered {}) - )); - - let vote_hooks = query_vote_hooks(&app, &proposal_module); - assert_eq!(vote_hooks.hooks[0], "votehook".to_string()); - - // Only DAO can remove vote hooks. - let err = remove_vote_hook_should_fail(&mut app, &proposal_module, CREATOR_ADDR, "votehook"); - assert!(matches!(err, ContractError::Unauthorized {})); - remove_vote_hook(&mut app, &proposal_module, core_addr.as_str(), "votehook"); - - let vote_hooks = query_vote_hooks(&app, &proposal_module); - assert!(vote_hooks.hooks.is_empty(),); - - // Can not remove that which does not exist. - let err = - remove_vote_hook_should_fail(&mut app, &proposal_module, core_addr.as_str(), "votehook"); - assert!(matches!( - err, - ContractError::HookError(HookError::HookNotRegistered {}) - )); -} - -#[test] -fn test_active_threshold_absolute() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_staking_active_threshold( - &mut app, - instantiate, - None, - Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - ); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let voting_module = query_voting_module(&app, &core_addr); - - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InactiveDao {})); - - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(100), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), gov_token, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Proposal creation now works as tokens have been staked to reach - // active threshold. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - // Unstake some tokens to make it inactive again. - let msg = cw20_stake::msg::ExecuteMsg::Unstake { - amount: Uint128::new(50), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), staking_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InactiveDao {})); -} - -#[test] -fn test_active_threshold_percent() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_staking_active_threshold( - &mut app, - instantiate, - None, - Some(ActiveThreshold::Percentage { - percent: Decimal::percent(20), - }), - ); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let voting_module = query_voting_module(&app, &core_addr); - - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InactiveDao {})); - - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(20_000_000), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), gov_token, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Proposal creation now works as tokens have been staked to reach - // active threshold. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - // Unstake some tokens to make it inactive again. - let msg = cw20_stake::msg::ExecuteMsg::Unstake { - amount: Uint128::new(1), // Only one is needed as we're right - // on the edge. :) - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), staking_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InactiveDao {})); -} - -#[test] -#[should_panic( - expected = "min_voting_period and max_voting_period must have the same units (height or time)" -)] -fn test_min_duration_unit_missmatch() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.min_voting_period = Some(Duration::Height(10)); - instantiate_with_staked_balances_governance(&mut app, instantiate, None); -} - -#[test] -#[should_panic(expected = "Min voting period must be less than or equal to max voting period")] -fn test_min_duration_larger_than_proposal_duration() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.min_voting_period = Some(Duration::Time(604801)); - instantiate_with_staked_balances_governance(&mut app, instantiate, None); -} - -#[test] -fn test_min_voting_period_no_early_pass() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.min_voting_period = Some(Duration::Height(10)); - instantiate.max_voting_period = Duration::Height(100); - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Open); - - app.update_block(|block| block.height += 10); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Passed); -} - -// Setting the min duration the same as the proposal duration just -// means that proposals cant close early. -#[test] -fn test_min_duration_same_as_proposal_duration() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.min_voting_period = Some(Duration::Height(100)); - instantiate.max_voting_period = Duration::Height(100); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "ekez".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "whale".to_string(), - amount: Uint128::new(90), - }, - ]), - ); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, "ekez", 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![]); - - // Whale votes yes. Normally the proposal would just pass and ekez - // would be out of luck. - vote_on_proposal(&mut app, &proposal_module, "whale", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "ekez", proposal_id, Vote::No); - - app.update_block(|b| b.height += 100); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Passed); -} - -#[test] -fn test_revoting_playthrough() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.allow_revoting = true; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - // Vote and change our minds a couple times. - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Open); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - ); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Open); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Open); - - // Can't cast the same vote more than once. - let err = vote_on_proposal_should_fail( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - assert!(matches!(err, ContractError::AlreadyCast {})); - - // Expire the proposal allowing the votes to be tallied. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - let proposal_response = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal_response.proposal.status, Status::Passed); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - // Can't vote once the proposal is passed. - let err = vote_on_proposal_should_fail( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - assert!(matches!(err, ContractError::Expired { .. })); -} - -/// Tests that revoting is stored at a per-proposal level. Proposals -/// created while revoting is enabled should not have it disabled if a -/// config change turns if off. -#[test] -fn test_allow_revoting_config_changes() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.allow_revoting = true; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - // This proposal should have revoting enable for its entire - // lifetime. - let revoting_proposal = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - // Update the config of the proposal module to disable revoting. - app.execute_contract( - core_addr.clone(), - proposal_module.clone(), - &ExecuteMsg::UpdateConfig { - veto: None, - threshold: Threshold::ThresholdQuorum { - quorum: PercentageThreshold::Percent(Decimal::percent(15)), - threshold: PercentageThreshold::Majority {}, - }, - max_voting_period: Duration::Height(10), - min_voting_period: None, - only_members_execute: true, - // Turn off revoting. - allow_revoting: false, - dao: core_addr.to_string(), - close_proposal_on_execution_failure: false, - }, - &[], - ) - .unwrap(); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let no_revoting_proposal = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - revoting_proposal, - Vote::Yes, - ); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - no_revoting_proposal, - Vote::Yes, - ); - - // Proposal without revoting should have passed. - let proposal_resp = query_proposal(&app, &proposal_module, no_revoting_proposal); - assert_eq!(proposal_resp.proposal.status, Status::Passed); - - // Proposal with revoting should not have passed. - let proposal_resp = query_proposal(&app, &proposal_module, revoting_proposal); - assert_eq!(proposal_resp.proposal.status, Status::Open); - - // Can change vote on the revoting proposal. - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - revoting_proposal, - Vote::No, - ); - // Expire the revoting proposal and close it. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - close_proposal(&mut app, &proposal_module, CREATOR_ADDR, revoting_proposal); -} - -/// Tests a simple three of five multisig configuration. -#[test] -fn test_three_of_five_multisig() { - let mut app = App::default(); - let mut instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); - instantiate.threshold = Threshold::AbsoluteCount { - threshold: Uint128::new(3), - }; - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_cw4_groups_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "one".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "two".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "three".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "four".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "five".to_string(), - amount: Uint128::new(1), - }, - ]), - ); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_module = core_state - .proposal_modules - .into_iter() - .next() - .unwrap() - .address; - - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::Yes); - - // Make sure it doesn't pass early. - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Open); - - vote_on_proposal(&mut app, &proposal_module, "three", proposal_id, Vote::Yes); - - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Passed); - - execute_proposal(&mut app, &proposal_module, "four", proposal_id); - - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Executed); - - // Make another proposal which we'll reject. - let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![]); - - vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::No); - vote_on_proposal(&mut app, &proposal_module, "three", proposal_id, Vote::No); - vote_on_proposal(&mut app, &proposal_module, "four", proposal_id, Vote::No); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Rejected); - - close_proposal(&mut app, &proposal_module, "four", proposal_id); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Closed); -} - -#[test] -fn test_three_of_five_multisig_revoting() { - let mut app = App::default(); - let mut instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); - instantiate.threshold = Threshold::AbsoluteCount { - threshold: Uint128::new(3), - }; - instantiate.allow_revoting = true; - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_cw4_groups_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "one".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "two".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "three".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "four".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "five".to_string(), - amount: Uint128::new(1), - }, - ]), - ); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_module = core_state - .proposal_modules - .into_iter() - .next() - .unwrap() - .address; - - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::Yes); - - // Make sure it doesn't pass early. - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Open); - - vote_on_proposal(&mut app, &proposal_module, "three", proposal_id, Vote::Yes); - - // Revoting is enabled so the proposal is still open. - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Open); - - // Change our minds. - vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::No); - vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::No); - - let err = - vote_on_proposal_should_fail(&mut app, &proposal_module, "two", proposal_id, Vote::No); - assert!(matches!(err, ContractError::AlreadyCast {})); - - // Expire the revoting proposal and close it. - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Rejected); -} - -/// Tests that absolute count style thresholds work with token style -/// voting. -#[test] -fn test_absolute_count_threshold_non_multisig() { - do_votes_staked_balances( - vec![ - TestSingleChoiceVote { - voter: "one".to_string(), - position: Vote::Yes, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - TestSingleChoiceVote { - voter: "two".to_string(), - position: Vote::No, - weight: Uint128::new(200), - should_execute: ShouldExecute::Yes, - }, - TestSingleChoiceVote { - voter: "three".to_string(), - position: Vote::Yes, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - ], - Threshold::AbsoluteCount { - threshold: Uint128::new(11), - }, - Status::Passed, - None, - ); -} - -/// Tests that we do not overflow when faced with really high token / -/// vote supply. -#[test] -fn test_large_absolute_count_threshold() { - do_votes_staked_balances( - vec![ - TestSingleChoiceVote { - voter: "two".to_string(), - position: Vote::No, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - // Can vote up to expiration time. - TestSingleChoiceVote { - voter: "one".to_string(), - position: Vote::Yes, - weight: Uint128::new(u128::MAX - 1), - should_execute: ShouldExecute::Yes, - }, - ], - Threshold::AbsoluteCount { - threshold: Uint128::new(u128::MAX), - }, - Status::Rejected, - None, - ); - - do_votes_staked_balances( - vec![ - TestSingleChoiceVote { - voter: "one".to_string(), - position: Vote::Yes, - weight: Uint128::new(u128::MAX - 1), - should_execute: ShouldExecute::Yes, - }, - TestSingleChoiceVote { - voter: "two".to_string(), - position: Vote::No, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - ], - Threshold::AbsoluteCount { - threshold: Uint128::new(u128::MAX), - }, - Status::Rejected, - None, - ); -} - -#[test] -fn test_proposal_count_initialized_to_zero() { - let mut app = App::default(); - let pre_propose_info = get_pre_propose_info(&mut app, None, false); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - veto: None, - threshold: Threshold::ThresholdQuorum { - threshold: PercentageThreshold::Majority {}, - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info, - close_proposal_on_execution_failure: true, - }, - Some(vec![ - Cw20Coin { - address: "ekez".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "innactive".to_string(), - amount: Uint128::new(90), - }, - ]), - ); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = core_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let proposal_single = proposal_modules.into_iter().next().unwrap().address; - - let proposal_count: u64 = app - .wrap() - .query_wasm_smart(proposal_single, &QueryMsg::ProposalCount {}) - .unwrap(); - assert_eq!(proposal_count, 0); -} - -#[test] -fn test_migrate_from_compatible() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - - let new_code_id = app.store_code(proposal_single_contract()); - let start_config = query_proposal_config(&app, &proposal_module); - - app.execute( - core_addr, - CosmosMsg::Wasm(WasmMsg::Migrate { - contract_addr: proposal_module.to_string(), - new_code_id, - msg: to_json_binary(&MigrateMsg::FromCompatible {}).unwrap(), - }), - ) - .unwrap(); - - let end_config = query_proposal_config(&app, &proposal_module); - assert_eq!(start_config, end_config); -} - -#[test] -pub fn test_migrate_updates_version() { - let mut deps = mock_dependencies(); - cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); - migrate(deps.as_mut(), mock_env(), MigrateMsg::FromCompatible {}).unwrap(); - let version = cw2::get_contract_version(&deps.storage).unwrap(); - assert_eq!(version.version, CONTRACT_VERSION); - assert_eq!(version.contract, CONTRACT_NAME); -} - -// //// TODO test migrate -// /// Instantiates a DAO with a v1 proposal module and then migrates it -// /// to v2. -// #[test] -// fn test_migrate_from_v1() { -// use cw_proposal_single_v1 as v1; -// use dao_pre_propose_single as cppbps; - -// let mut app = App::default(); -// let v1_proposal_single_code = app.store_code(v1_proposal_single_contract()); - -// let instantiate = v1::msg::InstantiateMsg { -// threshold: voting_v1::Threshold::AbsolutePercentage { -// percentage: voting_v1::PercentageThreshold::Majority {}, -// }, -// max_voting_period: cw_utils_v1::Duration::Height(6), -// min_voting_period: None, -// only_members_execute: false, -// allow_revoting: false, -// deposit_info: Some(v1::msg::DepositInfo { -// token: v1::msg::DepositToken::VotingModuleToken { -// token_type: VotingModuleTokenType::Cw20, -// }, -// deposit: Uint128::new(1), -// refund_failed_proposals: true, -// }), -// }; - -// let initial_balances = vec![Cw20Coin { -// amount: Uint128::new(100), -// address: CREATOR_ADDR.to_string(), -// }]; - -// let cw20_id = app.store_code(cw20_base_contract()); -// let cw20_stake_id = app.store_code(cw20_stake_contract()); -// let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); -// let core_contract_id = app.store_code(cw_core_contract()); - -// let instantiate_core = dao_interface::msg::InstantiateMsg { -// admin: None, -// name: "DAO DAO".to_string(), -// description: "A DAO that builds DAOs".to_string(), -// image_url: None, -// dao_uri: None, -// automatically_add_cw20s: true, -// automatically_add_cw721s: false, -// voting_module_instantiate_info: ModuleInstantiateInfo { -// code_id: staked_balances_voting_id, -// msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { -// active_threshold: None, -// token_info: dao_voting_cw20_staked::msg::TokenInfo::New { -// code_id: cw20_id, -// label: "DAO DAO governance token.".to_string(), -// name: "DAO DAO".to_string(), -// symbol: "DAO".to_string(), -// decimals: 6, -// initial_balances: initial_balances.clone(), -// marketing: None, -// staking_code_id: cw20_stake_id, -// unstaking_duration: Some(Duration::Height(6)), -// initial_dao_balance: None, -// }, -// }) -// .unwrap(), -// admin: None, -// funds: vec![], -// label: "DAO DAO voting module".to_string(), -// }, -// proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { -// code_id: v1_proposal_single_code, -// msg: to_json_binary(&instantiate).unwrap(), -// admin: Some(Admin::CoreModule {}), -// funds: vec![], -// label: "DAO DAO governance module.".to_string(), -// }], -// initial_items: None, -// }; - -// let core_addr = app -// .instantiate_contract( -// core_contract_id, -// Addr::unchecked(CREATOR_ADDR), -// &instantiate_core, -// &[], -// "DAO DAO", -// None, -// ) -// .unwrap(); - -// let core_state: dao_interface::query::DumpStateResponse = app -// .wrap() -// .query_wasm_smart( -// core_addr.clone(), -// &dao_interface::msg::QueryMsg::DumpState {}, -// ) -// .unwrap(); -// let voting_module = core_state.voting_module; - -// let staking_contract: Addr = app -// .wrap() -// .query_wasm_smart( -// voting_module.clone(), -// &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, -// ) -// .unwrap(); -// let token_contract: Addr = app -// .wrap() -// .query_wasm_smart( -// voting_module, -// &dao_interface::voting::Query::TokenContract {}, -// ) -// .unwrap(); - -// // Stake all the initial balances. -// for Cw20Coin { address, amount } in initial_balances { -// app.execute_contract( -// Addr::unchecked(address), -// token_contract.clone(), -// &cw20::Cw20ExecuteMsg::Send { -// contract: staking_contract.to_string(), -// amount, -// msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), -// }, -// &[], -// ) -// .unwrap(); -// } - -// // Update the block so that those staked balances appear. -// app.update_block(|block| block.height += 1); - -// let proposal_module = query_single_proposal_module(&app, &core_addr); - -// // Make a proposal so we can test that migration doesn't work with -// // open proposals that have deposits. -// mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); -// app.execute_contract( -// Addr::unchecked(CREATOR_ADDR), -// token_contract.clone(), -// &cw20::Cw20ExecuteMsg::IncreaseAllowance { -// spender: proposal_module.to_string(), -// amount: Uint128::new(1), -// expires: None, -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// Addr::unchecked(CREATOR_ADDR), -// proposal_module.clone(), -// &v1::msg::ExecuteMsg::Propose { -// title: "title".to_string(), -// description: "description".to_string(), -// msgs: vec![], -// }, -// &[], -// ) -// .unwrap(); - -// let v2_proposal_single = app.store_code(proposal_single_contract()); -// let pre_propose_single = app.store_code(pre_propose_single_contract()); - -// // Attempt to migrate. This will fail as there is a pending -// // proposal. -// let migrate_msg = MigrateMsg::FromV2 { timelock: None }; -// let err: ContractError = app -// .execute( -// core_addr.clone(), -// CosmosMsg::Wasm(WasmMsg::Migrate { -// contract_addr: proposal_module.to_string(), -// new_code_id: v2_proposal_single, -// msg: to_json_binary(&migrate_msg).unwrap(), -// }), -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert!(matches!(err, ContractError::PendingProposals {})); - -// // Vote on and close the pending proposal. -// vote_on_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1, Vote::No); -// close_proposal(&mut app, &proposal_module, CREATOR_ADDR, 1); - -// // Now we can migrate! -// app.execute( -// core_addr.clone(), -// CosmosMsg::Wasm(WasmMsg::Migrate { -// contract_addr: proposal_module.to_string(), -// new_code_id: v2_proposal_single, -// msg: to_json_binary(&migrate_msg).unwrap(), -// }), -// ) -// .unwrap(); - -// let new_config = query_proposal_config(&app, &proposal_module); -// assert_eq!( -// new_config, -// Config { -// timelock: None, -// threshold: Threshold::AbsolutePercentage { -// percentage: PercentageThreshold::Majority {} -// }, -// max_voting_period: Duration::Height(6), -// min_voting_period: None, -// only_members_execute: false, -// allow_revoting: false, -// dao: core_addr.clone(), -// close_proposal_on_execution_failure: true, -// } -// ); - -// // We can not migrate more than once. -// let err: ContractError = app -// .execute( -// core_addr.clone(), -// CosmosMsg::Wasm(WasmMsg::Migrate { -// contract_addr: proposal_module.to_string(), -// new_code_id: v2_proposal_single, -// msg: to_json_binary(&migrate_msg).unwrap(), -// }), -// ) -// .unwrap_err() -// .downcast() -// .unwrap(); -// assert!(matches!(err, ContractError::AlreadyMigrated {})); - -// // Make sure we can still query for ballots (rationale works post -// // migration). -// let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, 1); -// assert_eq!( -// vote.vote.unwrap(), -// VoteInfo { -// voter: Addr::unchecked(CREATOR_ADDR), -// vote: Vote::No, -// power: Uint128::new(100), -// rationale: None -// } -// ); - -// let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - -// // Check that a new creation policy has been birthed. -// let pre_propose = match proposal_creation_policy { -// ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), -// ProposalCreationPolicy::Module { addr } => addr, -// }; -// let pre_propose_config = query_pre_proposal_single_config(&app, &pre_propose); -// assert_eq!( -// pre_propose_config, -// cppbps::Config { -// open_proposal_submission: false, -// deposit_info: Some(CheckedDepositInfo { -// denom: CheckedDenom::Cw20(token_contract.clone()), -// amount: Uint128::new(1), -// refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, -// }) -// } -// ); - -// // Make sure we can still make a proposal and vote on it. -// mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); -// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); -// vote_on_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// proposal_id, -// Vote::Yes, -// ); -// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Executed); -// } - -// - Make a proposal that will fail to execute. -// - Verify that it goes to execution failed and that proposal -// deposits are returned once and not on closing. -// - Make the same proposal again. -// - Update the config to disable close on execution failure. -// - Make sure that proposal doesn't close on execution (this config -// feature gets applied retroactively). -#[test] -fn test_execution_failed() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token, - proposal_id, - } = setup_test(vec![BankMsg::Send { - to_address: "ekez".to_string(), - amount: coins(10, "ujuno"), - } - .into()]); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::ExecutionFailed); - - // Make sure the deposit was returned. - let balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - assert_eq!(balance, Uint128::new(10_000_000)); - - // ExecutionFailed is an end state. - let err = close_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - assert!(matches!(err, ContractError::WrongCloseStatus {})); - - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![BankMsg::Send { - to_address: "ekez".to_string(), - amount: coins(10, "ujuno"), - } - .into()], - ); - - let config = query_proposal_config(&app, &proposal_module); - - // Disable execution failing proposals. - app.execute_contract( - core_addr, - proposal_module.clone(), - &ExecuteMsg::UpdateConfig { - veto: None, - threshold: config.threshold, - max_voting_period: config.max_voting_period, - min_voting_period: config.min_voting_period, - only_members_execute: config.only_members_execute, - allow_revoting: config.allow_revoting, - dao: config.dao.into_string(), - // Disable. - close_proposal_on_execution_failure: false, - }, - &[], - ) - .unwrap(); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - let err: StdError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, StdError::Overflow { .. })); - - // Even though this proposal was created before the config change - // was made it still gets retroactively applied. - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Passed); - - // This proposal's deposit should not have been returned. It will - // not be returnable until this is executed, or close on execution - // is re-enabled. - let balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); - assert_eq!(balance, Uint128::zero()); -} - -#[test] -fn test_reply_proposal_mock() { - use crate::contract::reply; - use crate::state::PROPOSALS; - - let mut deps = mock_dependencies(); - let env = mock_env(); - - let m_proposal_id = mask_proposal_execution_proposal_id(1); - PROPOSALS - .save( - deps.as_mut().storage, - 1, - &SingleChoiceProposal { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: env.block.height, - expiration: cw_utils::Duration::Height(6).after(&env.block), - min_voting_period: None, - threshold: Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {}, - }, - allow_revoting: false, - total_power: Uint128::new(100_000_000), - msgs: vec![], - status: Status::Open, - veto: None, - votes: Votes::zero(), - }, - ) - .unwrap(); - - // PROPOSALS - let reply_msg = Reply { - id: m_proposal_id, - result: SubMsgResult::Err("error_msg".to_string()), - }; - let res = reply(deps.as_mut(), env, reply_msg).unwrap(); - assert_eq!( - res.attributes[0], - Attribute { - key: "proposal_execution_failed".to_string(), - value: 1.to_string() - } - ); - - let prop = PROPOSALS.load(deps.as_mut().storage, 1).unwrap(); - assert_eq!(prop.status, Status::ExecutionFailed); -} - -#[test] -fn test_proposal_too_large() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - let err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module, - &ExecuteMsg::Propose(ProposeMsg { - title: "".to_string(), - description: "a".repeat(MAX_PROPOSAL_SIZE as usize), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert!(matches!( - err, - ContractError::ProposalTooLarge { - size: _, - max: MAX_PROPOSAL_SIZE - } - )) -} - -#[test] -fn test_vote_not_registered() { - let CommonTest { - mut app, - core_addr: _, - proposal_module, - gov_token: _, - proposal_id, - } = setup_test(vec![]); - - let err = - vote_on_proposal_should_fail(&mut app, &proposal_module, "ekez", proposal_id, Vote::Yes); - assert!(matches!(err, ContractError::NotRegistered {})) -} - -#[test] -fn test_proposal_creation_permissions() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - - // Non pre-propose may not propose. - let err = app - .execute_contract( - Addr::unchecked("notprepropose"), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::Unauthorized {})); - - let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), - ProposalCreationPolicy::Module { addr } => addr, - }; - - // Proposer may not be none when a pre-propose module is making - // the proposal. - let err = app - .execute_contract( - pre_propose, - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: None, - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InvalidProposer {})); - - // Allow anyone to propose. - app.execute_contract( - core_addr, - proposal_module.clone(), - &ExecuteMsg::UpdatePreProposeInfo { - info: PreProposeInfo::AnyoneMayPropose {}, - }, - &[], - ) - .unwrap(); - - // Proposer must be None when non pre-propose module is making the - // proposal. - let err = app - .execute_contract( - Addr::unchecked("ekez"), - proposal_module.clone(), - &ExecuteMsg::Propose(ProposeMsg { - title: "title".to_string(), - description: "description".to_string(), - msgs: vec![], - proposer: Some("ekez".to_string()), - }), - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::InvalidProposer {})); - - // Works normally. - let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![]); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.proposer, Addr::unchecked("ekez")); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - ); - close_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -} - -#[test] -fn test_reply_hooks_mock() { - use crate::contract::reply; - use crate::state::{CREATION_POLICY, PROPOSAL_HOOKS, VOTE_HOOKS}; - - let mut deps = mock_dependencies(); - let env = mock_env(); - - // Add a proposal hook and remove it - let m_proposal_hook_idx = mask_proposal_hook_index(0); - PROPOSAL_HOOKS - .add_hook(deps.as_mut().storage, Addr::unchecked(CREATOR_ADDR)) - .unwrap(); - - let reply_msg = Reply { - id: m_proposal_hook_idx, - result: SubMsgResult::Err("error_msg".to_string()), - }; - - let res = reply(deps.as_mut(), env.clone(), reply_msg).unwrap(); - assert_eq!( - res.attributes[0], - Attribute { - key: "removed_proposal_hook".to_string(), - value: format! {"{CREATOR_ADDR}:{}", 0} - } - ); - - // Reply needs a creation policy in state. - CREATION_POLICY - .save( - deps.as_mut().storage, - &ProposalCreationPolicy::Module { - addr: Addr::unchecked("ekez"), - }, - ) - .unwrap(); - - let prepropose_reply_msg = Reply { - id: failed_pre_propose_module_hook_id(), - result: SubMsgResult::Err("error_msg".to_string()), - }; - - // Remove the pre-propose module as part of a reply. - let res = reply(deps.as_mut(), env.clone(), prepropose_reply_msg.clone()).unwrap(); - assert_eq!( - res.attributes[0], - Attribute { - key: "failed_prepropose_hook".to_string(), - value: "ekez".into() - } - ); - - // Do it again. This time, there is no module so this should error. - let _id = failed_pre_propose_module_hook_id(); - let err = reply(deps.as_mut(), env.clone(), prepropose_reply_msg).unwrap_err(); - assert!(matches!(err, ContractError::InvalidReplyID { id: _ })); - - // Check that we fail open. - let status = CREATION_POLICY.load(deps.as_ref().storage).unwrap(); - assert!(matches!(status, ProposalCreationPolicy::Anyone {})); - - // Vote hook - let m_vote_hook_idx = mask_vote_hook_index(0); - VOTE_HOOKS - .add_hook(deps.as_mut().storage, Addr::unchecked(CREATOR_ADDR)) - .unwrap(); - - let reply_msg = Reply { - id: m_vote_hook_idx, - result: SubMsgResult::Err("error_msg".to_string()), - }; - let res = reply(deps.as_mut(), env, reply_msg).unwrap(); - assert_eq!( - res.attributes[0], - Attribute { - key: "removed_vote_hook".to_string(), - value: format! {"{CREATOR_ADDR}:{}", 0} - } - ); -} - -#[test] -fn test_query_info() { - let CommonTest { - app, - core_addr: _, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - let info: InfoResponse = app - .wrap() - .query_wasm_smart(proposal_module, &QueryMsg::Info {}) - .unwrap(); - assert_eq!( - info, - InfoResponse { - info: ContractVersion { - contract: CONTRACT_NAME.to_string(), - version: CONTRACT_VERSION.to_string() - } - } - ) -} - -// Make a little multisig and test that queries to list votes work as -// expected. -#[test] -fn test_query_list_votes() { - let mut app = App::default(); - let mut instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); - instantiate.threshold = Threshold::AbsoluteCount { - threshold: Uint128::new(3), - }; - instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; - let core_addr = instantiate_with_cw4_groups_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "one".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "two".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "three".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "four".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "five".to_string(), - amount: Uint128::new(1), - }, - ]), - ); - let proposal_module = query_single_proposal_module(&app, &core_addr); - let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![]); - - let votes = query_list_votes(&app, &proposal_module, proposal_id, None, None); - assert_eq!(votes.votes, vec![]); - - vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::No); - vote_on_proposal(&mut app, &proposal_module, "three", proposal_id, Vote::No); - vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "four", proposal_id, Vote::Yes); - vote_on_proposal(&mut app, &proposal_module, "five", proposal_id, Vote::Yes); - - let votes = query_list_votes(&app, &proposal_module, proposal_id, None, None); - assert_eq!( - votes.votes, - vec![ - VoteInfo { - rationale: None, - voter: Addr::unchecked("five"), - vote: Vote::Yes, - power: Uint128::new(1) - }, - VoteInfo { - rationale: None, - voter: Addr::unchecked("four"), - vote: Vote::Yes, - power: Uint128::new(1) - }, - VoteInfo { - rationale: None, - voter: Addr::unchecked("one"), - vote: Vote::Yes, - power: Uint128::new(1) - }, - VoteInfo { - rationale: None, - voter: Addr::unchecked("three"), - vote: Vote::No, - power: Uint128::new(1) - }, - VoteInfo { - rationale: None, - voter: Addr::unchecked("two"), - vote: Vote::No, - power: Uint128::new(1) - } - ] - ); - - let votes = query_list_votes( - &app, - &proposal_module, - proposal_id, - Some("four".to_string()), - Some(2), - ); - assert_eq!( - votes.votes, - vec![ - VoteInfo { - rationale: None, - voter: Addr::unchecked("one"), - vote: Vote::Yes, - power: Uint128::new(1) - }, - VoteInfo { - rationale: None, - voter: Addr::unchecked("three"), - vote: Vote::No, - power: Uint128::new(1) - }, - ] - ); -} - -#[test] -fn test_update_pre_propose_module() { - let CommonTest { - mut app, - core_addr, - proposal_module, - gov_token, - proposal_id: pre_update_proposal_id, - } = setup_test(vec![]); - - // Store the address of the pre-propose module that we start with - // so we can execute withdraw on it later. - let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - let pre_propose_start = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), - ProposalCreationPolicy::Module { addr } => addr, - }; - - let pre_propose_id = app.store_code(pre_propose_single_contract()); - - // Make a proposal to switch to a new pre-propose moudle. - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![WasmMsg::Execute { - contract_addr: proposal_module.to_string(), - msg: to_json_binary(&ExecuteMsg::UpdatePreProposeInfo { - info: PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_id, - msg: to_json_binary(&dao_pre_propose_single::InstantiateMsg { - deposit_info: Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }), - open_proposal_submission: false, - extension: Empty::default(), - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "new pre-propose module".to_string(), - }, - }, - }) - .unwrap(), - funds: vec![], - } - .into()], - ); - - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - // Check that a new creation policy has been birthed. - let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), - ProposalCreationPolicy::Module { addr } => addr, - }; - - // Check that the admin has been set to the DAO properly. - let info: ContractInfoResponse = app - .wrap() - .query(&cosmwasm_std::QueryRequest::Wasm(WasmQuery::ContractInfo { - contract_addr: pre_propose.to_string(), - })) - .unwrap(); - assert_eq!(info.admin, Some(core_addr.to_string())); - - let pre_propose_config = query_pre_proposal_single_config(&app, &pre_propose); - assert_eq!( - pre_propose_config, - dao_pre_propose_single::Config { - deposit_info: Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(gov_token.clone()), - amount: Uint128::new(1), - refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, - }), - open_proposal_submission: false, - } - ); - - // Make a new proposal with this new module installed. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - // Check that the deposit was withdrawn. - let balance = query_balance_cw20(&app, gov_token.as_str(), CREATOR_ADDR); - assert_eq!(balance, Uint128::new(9_999_999)); - - // Vote on and execute the proposal created with the old - // module. This should work fine, but the deposit will not be - // returned as that module is no longer receiving hook messages. - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - pre_update_proposal_id, - Vote::Yes, - ); - execute_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - pre_update_proposal_id, - ); - - // Deposit should not have been returned. - let balance = query_balance_cw20(&app, gov_token.as_str(), CREATOR_ADDR); - assert_eq!(balance, Uint128::new(9_999_999)); - - // Withdraw from the old pre-propose module. - let proposal_id = make_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - vec![WasmMsg::Execute { - contract_addr: pre_propose_start.into_string(), - msg: to_json_binary(&dao_pre_propose_single::ExecuteMsg::Withdraw { denom: None }) - .unwrap(), - funds: vec![], - } - .into()], - ); - vote_on_proposal( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - ); - execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - - // Make sure the left over deposit was returned to the DAO. - let balance = query_balance_cw20(&app, gov_token.as_str(), core_addr.as_str()); - assert_eq!(balance, Uint128::new(10_000_000)); -} - -/// DAO should be admin of the pre-propose contract despite the fact -/// that the proposal module instantiates it. -#[test] -fn test_pre_propose_admin_is_dao() { - let CommonTest { - app, - core_addr, - proposal_module, - gov_token: _, - proposal_id: _, - } = setup_test(vec![]); - - let proposal_creation_policy = query_creation_policy(&app, &proposal_module); - - // Check that a new creation policy has been birthed. - let pre_propose = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => panic!("expected a pre-propose module"), - ProposalCreationPolicy::Module { addr } => addr, - }; - - let info: ContractInfoResponse = app - .wrap() - .query(&cosmwasm_std::QueryRequest::Wasm(WasmQuery::ContractInfo { - contract_addr: pre_propose.into_string(), - })) - .unwrap(); - assert_eq!(info.admin, Some(core_addr.into_string())); -} - -// I can add a rationale to my vote. My rational is queryable when -// listing votes. I can later change my rationale. -#[test] -fn test_rationale() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - .. - } = setup_test(vec![]); - - let rationale = Some("i support dog charities".to_string()); - - vote_on_proposal_with_rationale( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - rationale.clone(), - ); - - let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, proposal_id); - assert_eq!(vote.vote.unwrap().rationale, rationale); - - let rationale = - Some("i did not realize that dog charity was gambling with customer funds".to_string()); - - update_rationale( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - rationale.clone(), - ); - - let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, proposal_id); - assert_eq!(vote.vote.unwrap().rationale, rationale); -} - -// Revoting should override any previous rationale. If no new -// rationalle is provided, the old one will be wiped regardless. -#[test] -fn test_rational_clobbered_on_revote() { - let mut app = App::default(); - let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); - instantiate.allow_revoting = true; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let gov_token = query_dao_token(&app, &core_addr); - let proposal_module = query_single_proposal_module(&app, &core_addr); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - let rationale = Some("to_string".to_string()); - - vote_on_proposal_with_rationale( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::Yes, - rationale.clone(), - ); - - let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, proposal_id); - assert_eq!(vote.vote.unwrap().rationale, rationale); - - let rationale = None; - - // revote and clobber. - vote_on_proposal_with_rationale( - &mut app, - &proposal_module, - CREATOR_ADDR, - proposal_id, - Vote::No, - None, - ); - - let vote = query_vote(&app, &proposal_module, CREATOR_ADDR, proposal_id); - assert_eq!(vote.vote.unwrap().rationale, rationale); -} - -// Casting votes is only allowed within the proposal expiration timeframe -#[test] -pub fn test_not_allow_voting_on_expired_proposal() { - let CommonTest { - mut app, - core_addr: _, - proposal_module, - gov_token: _, - proposal_id, - } = setup_test(vec![]); - - // expire the proposal - app.update_block(|b| b.time = b.time.plus_seconds(604800)); - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Rejected); - assert_eq!(proposal.proposal.votes.yes, Uint128::zero()); - - // attempt to vote past the expiration date - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote: Vote::Yes, - rationale: None, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - // assert the vote got rejected and did not count - // towards the votes - let proposal = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(proposal.proposal.status, Status::Rejected); - assert_eq!(proposal.proposal.votes.yes, Uint128::zero()); - assert!(matches!(err, ContractError::Expired { id: _proposal_id })); -} - -#[test] -fn test_proposal_count_goes_up() { - let CommonTest { - mut app, - proposal_module, - gov_token, - core_addr, - .. - } = setup_test(vec![]); - - let next = query_next_proposal_id(&app, &proposal_module); - assert_eq!(next, 2); - - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - - let next = query_next_proposal_id(&app, &proposal_module); - assert_eq!(next, 3); } diff --git a/contracts/proposal/dao-proposal-single/src/v1_state.rs b/contracts/proposal/dao-proposal-single/src/v1_state.rs index 6c61d0f..783d165 100644 --- a/contracts/proposal/dao-proposal-single/src/v1_state.rs +++ b/contracts/proposal/dao-proposal-single/src/v1_state.rs @@ -1,163 +1,174 @@ -// //! Helper methods for migrating from v1 to v2 state. These will need -// //! to be updated when we bump our CosmWasm version for v2. +//! Helper methods for migrating from v1 to v2 state. These will need +//! to be updated when we bump our CosmWasm version for v2. -// use secret_utils::{Duration, Expiration}; -// use dao_voting::{ -// status::Status, -// threshold::{PercentageThreshold, Threshold}, -// voting::Votes, -// }; +use dao_voting::{ + status::Status, + threshold::{PercentageThreshold, Threshold}, + voting::Votes, +}; +use secret_utils::{Duration, Expiration}; -// pub fn v1_percentage_threshold_to_v2(v1: voting_v1::PercentageThreshold) -> PercentageThreshold { -// match v1 { -// voting_v1::PercentageThreshold::Majority {} => PercentageThreshold::Majority {}, -// voting_v1::PercentageThreshold::Percent(p) => PercentageThreshold::Percent(p), -// } -// } +pub fn v1_percentage_threshold_to_v2( + v1: dao_voting::threshold::PercentageThreshold, +) -> PercentageThreshold { + match v1 { + dao_voting::threshold::PercentageThreshold::Majority {} => PercentageThreshold::Majority {}, + dao_voting::threshold::PercentageThreshold::Percent(p) => PercentageThreshold::Percent(p), + } +} -// pub fn v1_threshold_to_v2(v1: voting_v1::Threshold) -> Threshold { -// match v1 { -// voting_v1::Threshold::AbsolutePercentage { percentage } => Threshold::AbsolutePercentage { -// percentage: v1_percentage_threshold_to_v2(percentage), -// }, -// voting_v1::Threshold::ThresholdQuorum { threshold, quorum } => Threshold::ThresholdQuorum { -// threshold: v1_percentage_threshold_to_v2(threshold), -// quorum: v1_percentage_threshold_to_v2(quorum), -// }, -// voting_v1::Threshold::AbsoluteCount { threshold } => Threshold::AbsoluteCount { threshold }, -// } -// } +pub fn v1_threshold_to_v2(v1: dao_voting::threshold::Threshold) -> Threshold { + match v1 { + dao_voting::threshold::Threshold::AbsolutePercentage { percentage } => { + Threshold::AbsolutePercentage { + percentage: v1_percentage_threshold_to_v2(percentage), + } + } + dao_voting::threshold::Threshold::ThresholdQuorum { threshold, quorum } => { + Threshold::ThresholdQuorum { + threshold: v1_percentage_threshold_to_v2(threshold), + quorum: v1_percentage_threshold_to_v2(quorum), + } + } + dao_voting::threshold::Threshold::AbsoluteCount { threshold } => { + Threshold::AbsoluteCount { threshold } + } + } +} -// pub fn v1_duration_to_v2(v1: cw_utils_v1::Duration) -> Duration { -// match v1 { -// cw_utils_v1::Duration::Height(height) => Duration::Height(height), -// cw_utils_v1::Duration::Time(time) => Duration::Time(time), -// } -// } +pub fn v1_duration_to_v2(v1: secret_utils::Duration) -> Duration { + match v1 { + secret_utils::Duration::Height(height) => Duration::Height(height), + secret_utils::Duration::Time(time) => Duration::Time(time), + } +} -// pub fn v1_expiration_to_v2(v1: cw_utils_v1::Expiration) -> Expiration { -// match v1 { -// cw_utils_v1::Expiration::AtHeight(height) => Expiration::AtHeight(height), -// cw_utils_v1::Expiration::AtTime(time) => Expiration::AtTime(time), -// cw_utils_v1::Expiration::Never {} => Expiration::Never {}, -// } -// } +pub fn v1_expiration_to_v2(v1: secret_utils::Expiration) -> Expiration { + match v1 { + secret_utils::Expiration::AtHeight(height) => Expiration::AtHeight(height), + secret_utils::Expiration::AtTime(time) => Expiration::AtTime(time), + secret_utils::Expiration::Never {} => Expiration::Never {}, + } +} -// pub fn v1_votes_to_v2(v1: voting_v1::Votes) -> Votes { -// Votes { -// yes: v1.yes, -// no: v1.no, -// abstain: v1.abstain, -// } -// } +pub fn v1_votes_to_v2(v1: dao_voting::voting::Votes) -> Votes { + Votes { + yes: v1.yes, + no: v1.no, + abstain: v1.abstain, + } +} -// pub fn v1_status_to_v2(v1: voting_v1::Status) -> Status { -// match v1 { -// voting_v1::Status::Open => Status::Open, -// voting_v1::Status::Rejected => Status::Rejected, -// voting_v1::Status::Passed => Status::Passed, -// voting_v1::Status::Executed => Status::Executed, -// voting_v1::Status::Closed => Status::Closed, -// } -// } +pub fn v1_status_to_v2(v1: dao_voting::status::Status) -> Status { + match v1 { + Status::Open => Status::Open, + Status::Rejected => Status::Rejected, + Status::Passed => Status::Passed, + Status::Executed => Status::Executed, + Status::Closed => Status::Closed, + Status::ExecutionFailed => Status::ExecutionFailed, + Status::VetoTimelock { expiration } => Status::VetoTimelock { expiration }, + Status::Vetoed => Status::Vetoed, + } +} -// #[cfg(test)] -// mod tests { -// use cosmwasm_std::{Decimal, Timestamp, Uint128}; +#[cfg(test)] +mod tests { + use cosmwasm_std::{Decimal, Timestamp, Uint128}; -// use super::*; + use super::*; -// #[test] -// fn test_percentage_conversion() { -// assert_eq!( -// v1_percentage_threshold_to_v2(voting_v1::PercentageThreshold::Majority {}), -// PercentageThreshold::Majority {} -// ); -// assert_eq!( -// v1_percentage_threshold_to_v2(voting_v1::PercentageThreshold::Percent( -// Decimal::percent(80) -// )), -// PercentageThreshold::Percent(Decimal::percent(80)) -// ) -// } + #[test] + fn test_percentage_conversion() { + assert_eq!( + v1_percentage_threshold_to_v2(dao_voting::threshold::PercentageThreshold::Majority {}), + PercentageThreshold::Majority {} + ); + assert_eq!( + v1_percentage_threshold_to_v2(dao_voting::threshold::PercentageThreshold::Percent( + Decimal::percent(80) + )), + PercentageThreshold::Percent(Decimal::percent(80)) + ) + } -// #[test] -// fn test_duration_conversion() { -// assert_eq!( -// v1_duration_to_v2(cw_utils_v1::Duration::Height(100)), -// Duration::Height(100) -// ); -// assert_eq!( -// v1_duration_to_v2(cw_utils_v1::Duration::Time(100)), -// Duration::Time(100) -// ); -// } + #[test] + fn test_duration_conversion() { + assert_eq!( + v1_duration_to_v2(secret_utils::Duration::Height(100)), + Duration::Height(100) + ); + assert_eq!( + v1_duration_to_v2(secret_utils::Duration::Time(100)), + Duration::Time(100) + ); + } -// #[test] -// fn test_expiration_conversion() { -// assert_eq!( -// v1_expiration_to_v2(cw_utils_v1::Expiration::AtHeight(100)), -// Expiration::AtHeight(100) -// ); -// assert_eq!( -// v1_expiration_to_v2(cw_utils_v1::Expiration::AtTime(Timestamp::from_seconds( -// 100 -// ))), -// Expiration::AtTime(Timestamp::from_seconds(100)) -// ); -// assert_eq!( -// v1_expiration_to_v2(cw_utils_v1::Expiration::Never {}), -// Expiration::Never {} -// ); -// } + #[test] + fn test_expiration_conversion() { + assert_eq!( + v1_expiration_to_v2(secret_utils::Expiration::AtHeight(100)), + Expiration::AtHeight(100) + ); + assert_eq!( + v1_expiration_to_v2(secret_utils::Expiration::AtTime(Timestamp::from_seconds( + 100 + ))), + Expiration::AtTime(Timestamp::from_seconds(100)) + ); + assert_eq!( + v1_expiration_to_v2(secret_utils::Expiration::Never {}), + Expiration::Never {} + ); + } -// #[test] -// fn test_threshold_conversion() { -// assert_eq!( -// v1_threshold_to_v2(voting_v1::Threshold::AbsoluteCount { -// threshold: Uint128::new(10) -// }), -// Threshold::AbsoluteCount { -// threshold: Uint128::new(10) -// } -// ); -// assert_eq!( -// v1_threshold_to_v2(voting_v1::Threshold::AbsolutePercentage { -// percentage: voting_v1::PercentageThreshold::Majority {} -// }), -// Threshold::AbsolutePercentage { -// percentage: PercentageThreshold::Majority {} -// } -// ); -// assert_eq!( -// v1_threshold_to_v2(voting_v1::Threshold::ThresholdQuorum { -// threshold: voting_v1::PercentageThreshold::Majority {}, -// quorum: voting_v1::PercentageThreshold::Percent(Decimal::percent(20)) -// }), -// Threshold::ThresholdQuorum { -// threshold: PercentageThreshold::Majority {}, -// quorum: PercentageThreshold::Percent(Decimal::percent(20)) -// } -// ); -// } + #[test] + fn test_threshold_conversion() { + assert_eq!( + v1_threshold_to_v2(dao_voting::threshold::Threshold::AbsoluteCount { + threshold: Uint128::new(10) + }), + Threshold::AbsoluteCount { + threshold: Uint128::new(10) + } + ); + assert_eq!( + v1_threshold_to_v2(dao_voting::threshold::Threshold::AbsolutePercentage { + percentage: dao_voting::threshold::PercentageThreshold::Majority {} + }), + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {} + } + ); + assert_eq!( + v1_threshold_to_v2(dao_voting::threshold::Threshold::ThresholdQuorum { + threshold: dao_voting::threshold::PercentageThreshold::Majority {}, + quorum: dao_voting::threshold::PercentageThreshold::Percent(Decimal::percent(20)) + }), + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(20)) + } + ); + } -// #[test] -// fn test_status_conversion() { -// macro_rules! status_conversion { -// ($x:expr) => { -// assert_eq!( -// v1_status_to_v2({ -// use voting_v1::Status; -// $x -// }), -// $x -// ) -// }; -// } + #[test] + fn test_status_conversion() { + macro_rules! status_conversion { + ($x:expr) => { + assert_eq!( + v1_status_to_v2({ + use dao_voting::status::Status; + $x + }), + $x + ) + }; + } -// status_conversion!(Status::Open); -// status_conversion!(Status::Closed); -// status_conversion!(Status::Executed); -// status_conversion!(Status::Rejected) -// } -// } + status_conversion!(Status::Open); + status_conversion!(Status::Closed); + status_conversion!(Status::Executed); + status_conversion!(Status::Rejected) + } +} diff --git a/contracts/voting/dao-voting-cw4/src/contract.rs b/contracts/voting/dao-voting-cw4/src/contract.rs index 54d0d7b..c383da3 100644 --- a/contracts/voting/dao-voting-cw4/src/contract.rs +++ b/contracts/voting/dao-voting-cw4/src/contract.rs @@ -42,7 +42,6 @@ pub fn instantiate( GroupContract::New { cw4_group_code_id, cw4_group_code_hash, - query_auth, initial_members, } => { println!("here"); @@ -83,7 +82,7 @@ pub fn instantiate( let msg = cw4_group_msg::Cw4GroupInstantiateMsg { admin: Some(info.sender.to_string()), members: initial_members, - query_auth, + query_auth: msg.query_auth, }; let sub_msg = SubMsg::reply_always( msg.to_cosmos_msg( diff --git a/contracts/voting/dao-voting-cw4/src/msg.rs b/contracts/voting/dao-voting-cw4/src/msg.rs index b5279b2..be58a98 100644 --- a/contracts/voting/dao-voting-cw4/src/msg.rs +++ b/contracts/voting/dao-voting-cw4/src/msg.rs @@ -12,7 +12,6 @@ pub enum GroupContract { cw4_group_code_id: u64, cw4_group_code_hash: String, initial_members: Vec, - query_auth: RawContract, }, } @@ -20,6 +19,7 @@ pub enum GroupContract { pub struct InstantiateMsg { pub group_contract: GroupContract, pub dao_code_hash: String, + pub query_auth: RawContract, } #[cw_serde] diff --git a/contracts/voting/dao-voting-cw4/src/tests.rs b/contracts/voting/dao-voting-cw4/src/tests.rs index fa748bb..25d96c2 100644 --- a/contracts/voting/dao-voting-cw4/src/tests.rs +++ b/contracts/voting/dao-voting-cw4/src/tests.rs @@ -132,12 +132,12 @@ fn _setup_test_case(app: &mut App) -> ContractInfo { cw4_group_code_id: cw4_instantiate_info.code_id, cw4_group_code_hash: cw4_instantiate_info.code_hash, initial_members: members, - query_auth: RawContract { - address: query_auth.address.to_string(), - code_hash: query_auth.code_hash, - }, }, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ) } @@ -157,12 +157,12 @@ fn test_instantiate() { cw4_group_code_id: cw4_instantiate_info.clone().code_id, cw4_group_code_hash: cw4_instantiate_info.clone().code_hash, initial_members: [].into(), - query_auth: RawContract { - address: query_auth.clone().address.to_string(), - code_hash: query_auth.clone().code_hash, - }, }, dao_code_hash: "dao_code_Hash".to_string(), + query_auth: RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, }; let _err = app .instantiate_contract( @@ -194,12 +194,12 @@ fn test_instantiate() { weight: 0, }, ], - query_auth: RawContract { - address: query_auth.address.to_string(), - code_hash: query_auth.code_hash, - }, }, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }; let _err = app .instantiate_contract( @@ -259,6 +259,10 @@ fn test_contract_info() { code_hash: cw4_info_with_member.clone().code_hash, }, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }; let voting_info = app .instantiate_contract( diff --git a/contracts/voting/dao-voting-snip20-staked/src/contract.rs b/contracts/voting/dao-voting-snip20-staked/src/contract.rs index c5d75ea..1e767a6 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/contract.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/contract.rs @@ -114,14 +114,13 @@ pub fn instantiate( staking_code_hash, label, unstaking_duration, - query_auth, } => { let init_msg = snip20_stake_msg::InstantiateMsg { owner: Some(info.sender.to_string()), unstaking_duration, token_address: address.to_string(), token_code_hash: Some(code_hash), - query_auth, + query_auth: msg.query_auth.clone(), }; let staking_contract = AnyContractInfo { addr: Addr::unchecked(""), @@ -158,7 +157,6 @@ pub fn instantiate( staking_code_id, staking_code_hash, unstaking_duration, - query_auth, } => { let initial_supply = initial_balances .iter() @@ -192,7 +190,7 @@ pub fn instantiate( STAKING_CONTRACT_UNSTAKING_DURATION.save(deps.storage, &unstaking_duration)?; STAKING_CONTRACT.save(deps.storage, &staking_contract)?; TOKEN_CONTRACT.save(deps.storage, &token_contract)?; - QUERY_AUTH.save(deps.storage, &query_auth)?; + QUERY_AUTH.save(deps.storage, &msg.query_auth)?; let init_msg = snip20_msg::InstantiateMsg { name, diff --git a/contracts/voting/dao-voting-snip20-staked/src/msg.rs b/contracts/voting/dao-voting-snip20-staked/src/msg.rs index b2158ff..f3e3ec1 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/msg.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/msg.rs @@ -30,7 +30,6 @@ pub enum StakingInfo { /// instantiation. This will be used when instantiating the /// new staking contract. unstaking_duration: Option, - query_auth: RawContract, }, } @@ -59,7 +58,6 @@ pub enum Snip20TokenInfo { staking_code_hash: String, unstaking_duration: Option, initial_dao_balance: Option, - query_auth: RawContract, }, } @@ -70,6 +68,7 @@ pub struct InstantiateMsg { /// for the DAO to be active pub active_threshold: Option, pub dao_code_hash: String, + pub query_auth: RawContract, } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] diff --git a/contracts/voting/dao-voting-snip20-staked/src/tests.rs b/contracts/voting/dao-voting-snip20-staked/src/tests.rs index e5f8d1e..122b02e 100644 --- a/contracts/voting/dao-voting-snip20-staked/src/tests.rs +++ b/contracts/voting/dao-voting-snip20-staked/src/tests.rs @@ -276,13 +276,13 @@ fn test_instantiate_zero_supply() { staking_code_id: staking_instantiate_info.code_id, staking_code_hash: staking_instantiate_info.code_hash, initial_dao_balance: Some(Uint128::zero()), - query_auth: RawContract { - address: query_auth.address.to_string(), - code_hash: query_auth.code_hash, - }, }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } @@ -311,13 +311,13 @@ fn test_instantiate_no_balances() { staking_code_id: staking_instantiate_info.code_id, staking_code_hash: staking_instantiate_info.code_hash, initial_dao_balance: Some(Uint128::zero()), - query_auth: RawContract { - address: query_auth.address.to_string(), - code_hash: query_auth.code_hash, - }, }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } @@ -360,6 +360,10 @@ fn test_instantiate_zero_active_threshold_count() { count: Uint128::new(0), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } @@ -399,6 +403,10 @@ fn test_contract_info() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -478,6 +486,10 @@ fn test_existing_snip20() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, }, ); @@ -626,6 +638,10 @@ fn test_existing_cw20_existing_staking() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, }, ); @@ -749,6 +765,10 @@ fn test_existing_cw20_existing_staking() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, &[], "voting module", @@ -799,6 +819,10 @@ fn test_different_heights() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, }, ); @@ -1031,6 +1055,10 @@ fn test_active_threshold_absolute_count() { count: Uint128::new(100), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -1122,6 +1150,10 @@ fn test_active_threshold_percent() { percent: Decimal::percent(20), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -1212,6 +1244,10 @@ fn test_active_threshold_percent_rounds_up() { percent: Decimal::percent(50), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -1321,6 +1357,10 @@ fn test_active_threshold_none() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -1388,6 +1428,10 @@ fn test_update_active_threshold() { }, active_threshold: None, dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); @@ -1491,6 +1535,10 @@ fn test_active_threshold_percentage_gt_100() { percent: Decimal::percent(120), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } @@ -1550,6 +1598,10 @@ fn test_active_threshold_percentage_lte_0() { percent: Decimal::percent(0), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } @@ -1609,6 +1661,10 @@ fn test_active_threshold_absolute_count_invalid() { count: Uint128::new(10000), }), dao_code_hash: "dao_code_hash".to_string(), + query_auth: RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, + }, }, ); } diff --git a/packages/dao-pre-propose-base/src/execute.rs b/packages/dao-pre-propose-base/src/execute.rs index b34eb3d..1b93bc6 100644 --- a/packages/dao-pre-propose-base/src/execute.rs +++ b/packages/dao-pre-propose-base/src/execute.rs @@ -98,7 +98,7 @@ where msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Propose { key, msg } => self.execute_propose(deps, env, info, key, msg), + ExecuteMsg::Propose { auth, msg } => self.execute_propose(deps, env, info, auth, msg), ExecuteMsg::UpdateConfig { deposit_info, open_proposal_submission, @@ -134,13 +134,9 @@ where deps: DepsMut, env: Env, info: MessageInfo, - key: String, + auth: Auth, msg: ProposalMessage, ) -> Result { - let auth = Auth::ViewingKey { - key, - address: info.sender.clone().to_string(), - }; self.check_can_submit(deps.as_ref(), auth)?; let config = self.config.load(deps.storage)?; @@ -233,7 +229,7 @@ where env: Env, info: MessageInfo, denom: Option, - key: String, + key: Option, ) -> Result { let dao = self.dao.load(deps.storage)?; if info.sender != dao.addr.clone() { @@ -249,7 +245,8 @@ where match denom { None => Err(PreProposeError::NoWithdrawalDenom {}), Some(denom) => { - let balance = denom.query_balance(&deps.querier, &env.contract.address, key)?; + let balance = + denom.query_balance(&deps.querier, &env.contract.address, key.unwrap())?; if balance.is_zero() { Err(PreProposeError::NothingToWithdraw {}) } else { diff --git a/packages/dao-pre-propose-base/src/msg.rs b/packages/dao-pre-propose-base/src/msg.rs index 9285b4b..c903853 100644 --- a/packages/dao-pre-propose-base/src/msg.rs +++ b/packages/dao-pre-propose-base/src/msg.rs @@ -5,6 +5,7 @@ use dao_voting::{ status::Status, }; use serde::{Deserialize, Serialize}; +use shade_protocol::basic_staking::Auth; #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] pub struct InstantiateMsg { @@ -26,7 +27,7 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { /// Creates a new proposal in the pre-propose module. MSG will be /// serialized and used as the proposal creation message. - Propose { key: String, msg: ProposalMessage }, + Propose { auth: Auth, msg: ProposalMessage }, /// Updates the configuration of this module. This will completely /// override the existing configuration. This new configuration @@ -66,7 +67,8 @@ pub enum ExecuteMsg { /// proposal deposits but are not longer used due to an /// `UpdateConfig` message being executed on the contract. denom: Option, - key: String, + /// Snip20 token vaiewing key for sender if denom is SNip20 instead of native + key: Option, }, /// Extension message. Contracts that extend this one should put diff --git a/packages/dao-testing/Cargo.toml b/packages/dao-testing/Cargo.toml new file mode 100644 index 0000000..7f0c407 --- /dev/null +++ b/packages/dao-testing/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "dao-testing" +authors = ["ekez ekez@withoutdoing.com", "Jake Hartnell "] +description = "Testing helper functions and interfaces for testing DAO modules." +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +[features] +# use test tube feature to enable test-tube integration tests, for example +# cargo test --features "test-tube" +test-tube = [] + +# This crate depends on multi-test and rand. These are not features in +# wasm builds of cosmwasm. Despite this crate only being used as a dev +# dependency, because it is part of the workspace it will always be +# compiled. There is no good way to remove a member from a workspace +# conditionally. As such, we don't compile anything here if we're +# targeting wasm. +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +secret-multi-test = { workspace = true } +secret-utils = { workspace = true } +secret-cw2 = { workspace = true } +snip20-reference-impl = { workspace = true } +cw4 = { workspace = true } +cw4-group = { workspace = true } +rand = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } + +cw-hooks = { workspace = true } +cw-vesting = { workspace = true } +snip20-stake = { workspace = true } +snip721-reference-impl = { workspace = true } +snip721-roles = { workspace = true } +dao-dao-core = { workspace = true} +dao-interface = { workspace = true } +dao-voting = { workspace = true } +dao-voting-snip20-staked = { workspace = true } +dao-voting-cw4 = { workspace = true } +dao-voting-snip721-staked = { workspace = true } +dao-voting-snip721-roles = { workspace = true } +dao-voting-token-staked = { workspace = true } +query_auth ={ workspace = true } +shade-protocol = { workspace = true } diff --git a/packages/dao-testing/README.md b/packages/dao-testing/README.md new file mode 100644 index 0000000..7086baa --- /dev/null +++ b/packages/dao-testing/README.md @@ -0,0 +1,4 @@ +# CosmWasm DAO Testing + +This package provides common testing functions and types for testing +DAO modules. diff --git a/packages/dao-testing/src/contracts.rs b/packages/dao-testing/src/contracts.rs new file mode 100644 index 0000000..1a02207 --- /dev/null +++ b/packages/dao-testing/src/contracts.rs @@ -0,0 +1,167 @@ +use cosmwasm_std::Empty; + +use dao_pre_propose_multiple as cppm; +use dao_pre_propose_single as cpps; +use secret_multi_test::{Contract, ContractWrapper}; + +pub fn snip20_base_contract() -> Box> { + let contract = ContractWrapper::new( + snip20_reference_impl::contract::execute, + snip20_reference_impl::contract::instantiate, + snip20_reference_impl::contract::query, + ); + Box::new(contract) +} + +pub fn cw4_group_contract() -> Box> { + let contract = ContractWrapper::new( + cw4_group::contract::execute, + cw4_group::contract::instantiate, + cw4_group::contract::query, + ); + Box::new(contract) +} + +pub fn snip721_base_contract() -> Box> { + let contract = ContractWrapper::new( + snip721_reference_impl::contract::execute, + snip721_reference_impl::contract::instantiate, + snip721_reference_impl::contract::query, + ); + Box::new(contract) +} + +pub fn snip721_roles_contract() -> Box> { + let contract = ContractWrapper::new( + snip721_roles::contract::execute, + snip721_roles::contract::instantiate, + snip721_roles::contract::query, + ); + Box::new(contract) +} + +pub fn snip20_stake_contract() -> Box> { + let contract = ContractWrapper::new( + snip20_stake::contract::execute, + snip20_stake::contract::instantiate, + snip20_stake::contract::query, + ); + Box::new(contract) +} + +pub fn proposal_condorcet_contract() -> Box> { + let contract = ContractWrapper::new( + dao_proposal_condorcet::contract::execute, + dao_proposal_condorcet::contract::instantiate, + dao_proposal_condorcet::contract::query, + ) + .with_reply(dao_proposal_condorcet::contract::reply); + Box::new(contract) +} + +pub fn proposal_single_contract() -> Box> { + let contract = ContractWrapper::new( + dao_proposal_single::contract::execute, + dao_proposal_single::contract::instantiate, + dao_proposal_single::contract::query, + ) + .with_reply(dao_proposal_single::contract::reply) + .with_migrate(dao_proposal_single::contract::migrate); + Box::new(contract) +} + +pub fn pre_propose_single_contract() -> Box> { + let contract = ContractWrapper::new( + cpps::contract::execute, + cpps::contract::instantiate, + cpps::contract::query, + ); + Box::new(contract) +} + +pub fn pre_propose_multiple_contract() -> Box> { + let contract = ContractWrapper::new( + cppm::contract::execute, + cppm::contract::instantiate, + cppm::contract::query, + ); + Box::new(contract) +} + +pub fn snip20_staked_balances_voting_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_snip20_staked::contract::execute, + dao_voting_snip20_staked::contract::instantiate, + dao_voting_snip20_staked::contract::query, + ) + .with_reply(dao_voting_snip20_staked::contract::reply); + Box::new(contract) +} + +pub fn native_staked_balances_voting_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_token_staked::contract::execute, + dao_voting_token_staked::contract::instantiate, + dao_voting_token_staked::contract::query, + ); + Box::new(contract) +} + +pub fn voting_snip721_staked_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_snip721_staked::contract::execute, + dao_voting_snip721_staked::contract::instantiate, + dao_voting_snip721_staked::contract::query, + ) + .with_reply(dao_voting_snip721_staked::contract::reply); + Box::new(contract) +} + +pub fn dao_dao_contract() -> Box> { + let contract = ContractWrapper::new( + dao_dao_core::contract::execute, + dao_dao_core::contract::instantiate, + dao_dao_core::contract::query, + ) + .with_reply(dao_dao_core::contract::reply) + .with_migrate(dao_dao_core::contract::migrate); + Box::new(contract) +} + +pub fn dao_voting_cw4_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_cw4::contract::execute, + dao_voting_cw4::contract::instantiate, + dao_voting_cw4::contract::query, + ) + .with_reply(dao_voting_cw4::contract::reply); + Box::new(contract) +} + +pub fn dao_voting_snip721_roles_contract() -> Box> { + let contract = ContractWrapper::new( + dao_voting_snip721_roles::contract::execute, + dao_voting_snip721_roles::contract::instantiate, + dao_voting_snip721_roles::contract::query, + ) + .with_reply(dao_voting_snip721_roles::contract::reply); + Box::new(contract) +} + +pub fn cw_vesting_contract() -> Box> { + let contract = ContractWrapper::new( + cw_vesting::contract::execute, + cw_vesting::contract::instantiate, + cw_vesting::contract::query, + ); + Box::new(contract) +} + +pub fn query_auth_contract() -> Box> { + let contract = ContractWrapper::new( + query_auth::contract::execute, + query_auth::contract::instantiate, + query_auth::contract::query, + ); + Box::new(contract) +} diff --git a/packages/dao-testing/src/lib.rs b/packages/dao-testing/src/lib.rs new file mode 100644 index 0000000..d7fb3b5 --- /dev/null +++ b/packages/dao-testing/src/lib.rs @@ -0,0 +1,7 @@ +#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] + +#[cfg(not(target_arch = "wasm32"))] +pub mod tests; + +#[cfg(not(target_arch = "wasm32"))] +pub use tests::*; diff --git a/packages/dao-testing/src/tests.rs b/packages/dao-testing/src/tests.rs new file mode 100644 index 0000000..d573773 --- /dev/null +++ b/packages/dao-testing/src/tests.rs @@ -0,0 +1,635 @@ +use cosmwasm_std::{Decimal, Uint128}; +use dao_voting::status::Status; +use dao_voting::threshold::{PercentageThreshold, Threshold}; +use dao_voting::voting::Vote; +use rand::{prelude::SliceRandom, Rng}; + +/// If a test vote should execute. Used for fuzzing and checking that +/// votes after a proposal has completed aren't allowed. +pub enum ShouldExecute { + /// This should execute. + Yes, + /// This should not execute. + No, + /// Doesn't matter. + Meh, +} + +pub struct TestSingleChoiceVote { + /// The address casting the vote. + pub voter: String, + /// Position on the vote. + pub position: Vote, + /// Voting power of the address. + pub weight: Uint128, + /// If this vote is expected to execute. + pub should_execute: ShouldExecute, +} + +pub fn test_simple_votes(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Passed, + None, + ); + + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Rejected, + None, + ) +} + +pub fn test_simple_vote_no_overflow(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(u128::max_value()), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Passed, + None, + ); +} + +pub fn test_vote_no_overflow(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(u128::max_value()), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Passed, + None, + ); + + do_votes( + vec![ + TestSingleChoiceVote { + voter: "zeke".to_string(), + position: Vote::No, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(u128::max_value() - 1), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(99)), + }, + Status::Passed, + None, + ) +} + +pub fn test_simple_early_rejection(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "zeke".to_string(), + position: Vote::No, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Rejected, + None, + ); + + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(99)), + }, + Status::Open, + Some(Uint128::from(u128::max_value())), + ); +} + +pub fn test_vote_abstain_only(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(u64::max_value().into()), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + Status::Rejected, + None, + ); + + // The quorum shouldn't matter here in determining if the vote is + // rejected. + for i in 0..101 { + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(u64::max_value().into()), + should_execute: ShouldExecute::Yes, + }], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(100)), + quorum: PercentageThreshold::Percent(Decimal::percent(i)), + }, + Status::Rejected, + None, + ); + } +} + +pub fn test_tricky_rounding(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + // This tests the smallest possible round up for passing + // thresholds we can have. Specifically, a 1% passing threshold + // and 1 total vote. This should round up and only pass if there + // are more than 1 yes votes. + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(1)), + }, + Status::Passed, + Some(Uint128::new(100)), + ); + + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(1)), + }, + Status::Passed, + Some(Uint128::new(1000)), + ); + + // HIGH PERCISION + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(9999999), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(1)), + }, + Status::Open, + Some(Uint128::new(1000000000)), + ); + + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(1)), + }, + Status::Rejected, + None, + ); +} + +pub fn test_no_double_votes(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(2), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(2), + should_execute: ShouldExecute::No, + }, + ], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(100)), + }, + // NOTE: Updating our cw20-base version will cause this to + // fail. In versions of cw20-base before Feb 15 2022 (the one + // we use at the time of writing) it was allowed to have an + // initial balance that repeats for a given address but it + // would cause miscalculation of the total supply. In this + // case the total supply is miscumputed to be 4 so this is + // assumed to have 2 abstain votes out of 4 possible votes. + Status::Open, + Some(Uint128::new(10)), + ) +} + +pub fn test_votes_favor_yes(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::No, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "ezek".to_string(), + position: Vote::Yes, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(50)), + }, + Status::Passed, + None, + ); + + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(50)), + }, + Status::Passed, + None, + ); + + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + // Can vote up to expiration time. + TestSingleChoiceVote { + voter: "ezek".to_string(), + position: Vote::No, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::percent(50)), + }, + Status::Passed, + None, + ); +} + +pub fn test_votes_low_threshold(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(10)), + quorum: PercentageThreshold::Majority {}, + }, + Status::Passed, + None, + ); + + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(5), + should_execute: ShouldExecute::Yes, + }, + // Can vote up to expiration time. + TestSingleChoiceVote { + voter: "ezek".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(10)), + quorum: PercentageThreshold::Majority {}, + }, + Status::Passed, + None, + ); +} + +pub fn test_majority_vs_half(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(50)), + quorum: PercentageThreshold::Majority {}, + }, + Status::Passed, + None, + ); + + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + // Can vote up to expiration time, even if it already rejected. + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Majority {}, + }, + Status::Rejected, + None, + ); +} + +pub fn test_pass_threshold_not_quorum(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(59), + should_execute: ShouldExecute::Yes, + }], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(60)), + }, + Status::Open, + Some(Uint128::new(100)), + ); + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(59), + should_execute: ShouldExecute::Yes, + }], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(60)), + }, + // As the threshold is 50% and 59% of voters have voted no + // this is unable to pass. + Status::Rejected, + Some(Uint128::new(100)), + ); +} + +pub fn test_pass_exactly_quorum(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(60), + should_execute: ShouldExecute::Yes, + }], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(60)), + }, + Status::Passed, + Some(Uint128::new(100)), + ); + do_votes( + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(59), + should_execute: ShouldExecute::Yes, + }, + // This is an intersting one because in this case the no + // voter is actually incentivised not to vote. By voting + // they move the quorum over the threshold and pass the + // vote. In a DAO with sufficently involved stakeholders + // no voters should effectively never vote if there is a + // quorum higher than the threshold as it makes the + // passing threshold the quorum threshold. + TestSingleChoiceVote { + voter: "keze".to_string(), + position: Vote::No, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + ], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(60)), + }, + Status::Passed, + Some(Uint128::new(100)), + ); + do_votes( + vec![TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::No, + weight: Uint128::new(60), + should_execute: ShouldExecute::Yes, + }], + Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(60)), + }, + Status::Rejected, + Some(Uint128::new(100)), + ); +} + +pub fn fuzz_voting(do_votes: F) +where + F: Fn(Vec, Threshold, Status, Option), +{ + let mut rng = rand::thread_rng(); + let dist = rand::distributions::Uniform::::new(1, 200); + for _ in 0..10 { + let yes: Vec = (0..50).map(|_| rng.sample(dist)).collect(); + let no: Vec = (0..50).map(|_| rng.sample(dist)).collect(); + + let yes_sum: u64 = yes.iter().sum(); + let no_sum: u64 = no.iter().sum(); + let expected_status = match yes_sum.cmp(&no_sum) { + std::cmp::Ordering::Less => Status::Rejected, + // Depends on which reaches the threshold first. Ignore for now. + std::cmp::Ordering::Equal => Status::Rejected, + std::cmp::Ordering::Greater => Status::Passed, + }; + + let yes = yes + .into_iter() + .enumerate() + .map(|(idx, weight)| TestSingleChoiceVote { + voter: format!("yes_{idx}"), + position: Vote::Yes, + weight: Uint128::new(weight as u128), + should_execute: ShouldExecute::Meh, + }); + let no = no + .into_iter() + .enumerate() + .map(|(idx, weight)| TestSingleChoiceVote { + voter: format!("no_{idx}"), + position: Vote::No, + weight: Uint128::new(weight as u128), + should_execute: ShouldExecute::Meh, + }); + let mut votes = yes.chain(no).collect::>(); + votes.shuffle(&mut rng); + + do_votes( + votes, + Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }, + expected_status, + None, + ); + } +}