From ede9401f6cf3ee9e8ff94a6ceca114781a44fbcd Mon Sep 17 00:00:00 2001 From: bekauz Date: Fri, 1 Dec 2023 15:01:22 +0100 Subject: [PATCH] revert v2 migration on prop-single --- Cargo.lock | 11 +- Cargo.toml | 5 +- .../proposal/dao-proposal-single/Cargo.toml | 16 +- .../dao-proposal-single/src/contract.rs | 83 ++-- .../proposal/dao-proposal-single/src/lib.rs | 2 +- .../proposal/dao-proposal-single/src/msg.rs | 21 +- .../src/testing/migration_tests.rs | 420 +++++++----------- .../dao-proposal-single/src/v1_state.rs | 163 +++++++ .../dao-proposal-single/src/v2_state.rs | 128 ------ 9 files changed, 400 insertions(+), 449 deletions(-) create mode 100644 contracts/proposal/dao-proposal-single/src/v1_state.rs delete mode 100644 contracts/proposal/dao-proposal-single/src/v2_state.rs diff --git a/Cargo.lock b/Cargo.lock index d260a3870..2afdeaa4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2024,11 +2024,13 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "cw-core", "cw-denom 2.3.0", "cw-hooks 2.3.0", "cw-multi-test", + "cw-proposal-single", "cw-storage-plus 1.2.0", - "cw-utils 0.16.0", + "cw-utils 0.13.4", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", @@ -2039,24 +2041,19 @@ dependencies = [ "cw4-group 1.1.2", "cw721-base 0.18.0", "dao-dao-core 2.3.0", - "dao-dao-core 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dao-dao-macros 2.3.0", "dao-hooks 2.3.0", "dao-interface 2.3.0", - "dao-interface 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dao-pre-propose-base 2.3.0", "dao-pre-propose-single 2.3.0", - "dao-pre-propose-single 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dao-proposal-single 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dao-testing", + "dao-voting 0.1.0", "dao-voting 2.3.0", - "dao-voting 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dao-voting-cw20-balance", "dao-voting-cw20-staked", "dao-voting-cw4", "dao-voting-cw721-staked", "dao-voting-token-staked", - "semver", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 428f88bca..b588c50f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,6 @@ test-context = "0.1" thiserror = {version = "1.0"} token-bindings = "0.11.0" wynd-utils = "0.4" -semver = "1" # One commit ahead of version 0.3.0. Allows initialization with an # optional owner. @@ -128,10 +127,12 @@ cw4-voting-v1 = { package = "cw4-voting", version = "0.1.0" } voting-v1 = { package = "dao-voting", version = "0.1.0" } stake-cw20-v03 = { package = "stake-cw20", version = "0.2.6" } + # v2 dependencies. used for state migrations cw-utils-v2 = { package = "cw-utils", version = "0.16" } dao-dao-core-v2 = { package = "dao-dao-core", version = "2.2.0" } dao-interface-v2 = { package = "dao-interface", version = "2.2.0" } dao-proposal-single-v2 = { package = "dao-proposal-single", version = "2.2.0" } dao-pre-propose-single-v2 = { package = "dao-pre-propose-single", version = "2.2.0" } -voting-v2 = { package = "dao-voting", version = "2.2.0" } +voting-v2 = { package = "dao-voting", version = "2.2.0"} + diff --git a/contracts/proposal/dao-proposal-single/Cargo.toml b/contracts/proposal/dao-proposal-single/Cargo.toml index 8c61ae26d..eb23de7f3 100644 --- a/contracts/proposal/dao-proposal-single/Cargo.toml +++ b/contracts/proposal/dao-proposal-single/Cargo.toml @@ -34,13 +34,9 @@ dao-voting = { workspace = true } cw-hooks = { workspace = true } dao-hooks = { workspace = true } -# Deps required for v2 migration -cw-utils-v2 = { workspace = true } -voting-v2 = { workspace = true } -dao-proposal-single-v2 = { workspace = true, features = ["library"] } - -# deps for v3 migration -semver = { workspace = true, features = [] } +cw-utils-v1 = { workspace = true} +voting-v1 = { workspace = true } +cw-proposal-single-v1 = { workspace = true, features = ["library"] } [dev-dependencies] cosmwasm-schema = { workspace = true } @@ -59,8 +55,4 @@ cw20-base = { workspace = true } cw721-base = { workspace = true } cw4 = { workspace = true } cw4-group = { workspace = true } - -# Required for migration testing -dao-dao-core-v2 = { workspace = true, features = ["library"] } -dao-interface-v2 = { workspace = true } -dao-pre-propose-single-v2 = { workspace = true, features = ["library"] } +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 984c2712f..857c4f48d 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -24,14 +24,11 @@ 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 semver::Version; -use std::str::FromStr; use crate::msg::MigrateMsg; use crate::proposal::{next_proposal_id, SingleChoiceProposal}; use crate::state::{Config, CREATION_POLICY}; - -use crate::v2_state::{v2_status_to_v3, v2_threshold_to_v3, v2_votes_to_v3}; +use cw_proposal_single_v1 as v1; use crate::{ error::ContractError, msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, @@ -40,7 +37,9 @@ use crate::{ query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, state::{Ballot, BALLOTS, CONFIG, PROPOSALS, PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS}, }; - +use crate::v1_state::{ + v1_duration_to_v2, v1_expiration_to_v2, v1_status_to_v2, v1_threshold_to_v2, v1_votes_to_v2, +}; pub(crate) const CONTRACT_NAME: &str = "crates.io:dao-proposal-single"; pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -952,49 +951,45 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - let version_2 = Version::new(2, 0, 0); - + 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. - let target_blob_version = Version::from_str(CONTRACT_VERSION) - .map_err(|_| ContractError::MigrationVersionError {})?; - let parsed_version = - Version::from_str(&version).map_err(|_| ContractError::MigrationVersionError {})?; - - // If the version in storage matches the version in the blob + // 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 parsed_version == target_blob_version { + if version == CONTRACT_VERSION { return Err(ContractError::AlreadyMigrated {}); } - // migration is only possible from 2.0.0, but no later than the - // version of the blob we are migrating to - if parsed_version < version_2 || parsed_version > target_blob_version { - return Err(ContractError::MigrationVersionError {}); - } - - // Update the stored config to have the new `veto` field - let current_config = dao_proposal_single_v2::state::CONFIG.load(deps.storage)?; + // Update the stored config to have the new + // `close_proposal_on_execution_failure` field. + let current_config = v1::state::CONFIG.load(deps.storage)?; CONFIG.save( deps.storage, &Config { - threshold: v2_threshold_to_v3(current_config.threshold), - max_voting_period: current_config.max_voting_period, - min_voting_period: current_config.min_voting_period, + threshold: v1_threshold_to_v2(current_config.threshold), + max_voting_period: v1_duration_to_v2(current_config.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: current_config - .close_proposal_on_execution_failure, + close_proposal_on_execution_failure, veto, }, )?; - // Update the module's proposals to v3. - let current_proposals = dao_proposal_single_v2::state::PROPOSALS + 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::>>()?; + .collect::>>()?; // Based on gas usage testing, we estimate that we will be // able to migrate ~4200 proposals at a time before @@ -1002,8 +997,12 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result>(|(id, prop)| { - if prop.status != voting_v2::status::Status::Closed - && prop.status != voting_v2::status::Status::Executed + 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. @@ -1015,13 +1014,13 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result Result Ok(Response::default() .add_attribute("action", "migrate") .add_attribute("from", "compatible")), diff --git a/contracts/proposal/dao-proposal-single/src/lib.rs b/contracts/proposal/dao-proposal-single/src/lib.rs index 6d2cc19bb..b556f8b06 100644 --- a/contracts/proposal/dao-proposal-single/src/lib.rs +++ b/contracts/proposal/dao-proposal-single/src/lib.rs @@ -10,6 +10,6 @@ pub mod query; mod testing; pub mod state; -mod v2_state; +pub mod v1_state; pub use crate::error::ContractError; diff --git a/contracts/proposal/dao-proposal-single/src/msg.rs b/contracts/proposal/dao-proposal-single/src/msg.rs index d54135395..f98eecdfa 100644 --- a/contracts/proposal/dao-proposal-single/src/msg.rs +++ b/contracts/proposal/dao-proposal-single/src/msg.rs @@ -213,7 +213,26 @@ pub enum QueryMsg { #[cw_serde] pub enum MigrateMsg { - FromV2 { + FromV1 { + /// This field was not present in DAO DAO v1. To migrate, a + /// value must be specified. + /// + /// If set to true proposals will be closed if their execution + /// fails. Otherwise, proposals will remain open after execution + /// failure. For example, with this enabled a proposal to send 5 + /// tokens out of a DAO's treasury with 4 tokens would be closed when + /// it is executed. With this disabled, that same proposal would + /// remain open until the DAO's treasury was large enough for it to be + /// executed. + close_proposal_on_execution_failure: bool, + /// This field was not present in DAO DAO v1. To migrate, a + /// value must be specified. + /// + /// This contains information about how a pre-propose module may be configured. + /// If set to "AnyoneMayPropose", there will be no pre-propose module and consequently, + /// no deposit or membership checks when submitting a proposal. The "ModuleMayPropose" + /// option allows for instantiating a prepropose module which will handle deposit verification and return logic. + pre_propose_info: PreProposeInfo, /// This field was not present in DAO DAO v2. To migrate, a /// value must be specified. /// 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 1b9c05a81..228349d65 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs @@ -1,20 +1,24 @@ -use crate::msg::QueryMsg; 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, v2_dao_dao_contract, v2_proposal_single_contract, + dao_dao_contract, proposal_single_contract, v1_dao_dao_contract, v1_proposal_single_contract, }; +use dao_voting::{deposit::UncheckedDepositInfo, status::Status}; +use dao_voting::veto::VetoConfig; use crate::testing::{ execute::{execute_proposal, make_proposal, vote_on_proposal}, - queries::query_proposal_count, + instantiate::get_pre_propose_info, + queries::{query_proposal, query_proposal_count}, }; +use crate::testing::queries::query_list_proposals; /// This test attempts to simulate a realistic migration from DAO DAO -/// v2 to v3. Other tests in `/tests/tests.rs` check that versions and +/// 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 @@ -31,20 +35,19 @@ use crate::testing::{ /// /// - Items are not overriden during migration. #[test] -fn test_v2_v3_full_migration() { +fn test_v1_v2_full_migration() { let sender = Addr::unchecked("sender"); let mut app = App::default(); // ---- - // instantiate a v2 DAO + // instantiate a v1 DAO // ---- - let proposal_code = app.store_code(v2_proposal_single_contract()); - // let pre_proposal_code = app.store_code(v2_pre_propose_single_contract()); - let core_code = app.store_code(v2_dao_dao_contract()); + let proposal_code = app.store_code(v1_proposal_single_contract()); + let core_code = app.store_code(v1_dao_dao_contract()); - // cw20 staking and voting module has not changed across v2->v3 so + // 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()); @@ -59,14 +62,14 @@ fn test_v2_v3_full_migration() { .instantiate_contract( core_code, sender.clone(), - &dao_interface_v2::msg::InstantiateMsg { + &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: dao_interface_v2::state::ModuleInstantiateInfo { + 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, @@ -83,54 +86,30 @@ fn test_v2_v3_full_migration() { initial_dao_balance: Some(Uint128::new(100)), }, }) - .unwrap(), - admin: Some(dao_interface_v2::state::Admin::CoreModule {}), + .unwrap(), + admin: cw_core_v1::msg::Admin::CoreContract {}, label: "voting".to_string(), - funds: vec![], }, - proposal_modules_instantiate_info: vec![ - dao_interface_v2::state::ModuleInstantiateInfo { - code_id: proposal_code, - msg: to_json_binary(&dao_proposal_single_v2::msg::InstantiateMsg { - threshold: voting_v2::threshold::Threshold::AbsolutePercentage { - percentage: voting_v2::threshold::PercentageThreshold::Majority {}, - }, - max_voting_period: cw_utils::Duration::Height(6), - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - pre_propose_info: - // TODO use pre-propose module - // voting_v2::pre_propose::PreProposeInfo::ModuleMayPropose { - // info: dao_interface_v2::state::ModuleInstantiateInfo { - // code_id: pre_proposal_code, - // msg: to_json_binary( - // &dao_pre_propose_single_v2::InstantiateMsg { - // deposit_info: None, - // open_proposal_submission: false, - // extension: cosmwasm_std::Empty {}, - // }, - // ) - // .unwrap(), - // admin: Some(dao_interface_v2::state::Admin::CoreModule {}), - // funds: vec![], - // label: "pre-propose module".to_string(), - // }, - // }, - voting_v2::pre_propose::PreProposeInfo::AnyoneMayPropose {}, - close_proposal_on_execution_failure: true, - }) + 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: Some(dao_interface_v2::state::Admin::CoreModule {}), - funds: vec![], - label: "proposal".to_string(), - }, - ], - initial_items: Some(vec![dao_interface_v2::msg::InitialItem { + 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(), }]), - dao_uri: None, }, &[], "core", @@ -143,9 +122,9 @@ fn test_v2_v3_full_migration() { contract_addr: core.to_string(), admin: core.to_string(), } - .into(), + .into(), ) - .unwrap(); + .unwrap(); // ---- // stake tokens in the DAO @@ -154,7 +133,7 @@ fn test_v2_v3_full_migration() { let token = { let voting: Addr = app .wrap() - .query_wasm_smart(&core, &dao_interface_v2::msg::QueryMsg::VotingModule {}) + .query_wasm_smart(&core, &cw_core_v1::msg::QueryMsg::VotingModule {}) .unwrap(); let staking: Addr = app .wrap() @@ -180,7 +159,7 @@ fn test_v2_v3_full_migration() { }, &[], ) - .unwrap(); + .unwrap(); app.update_block(next_block); token }; @@ -190,80 +169,70 @@ fn test_v2_v3_full_migration() { // ---- let proposal = { - let modules: Vec = app + let modules: Vec = app .wrap() .query_wasm_smart( &core, - &dao_interface_v2::msg::QueryMsg::ProposalModules { - start_after: None, + &cw_core_v1::msg::QueryMsg::ProposalModules { + start_at: None, limit: None, }, ) .unwrap(); assert!(modules.len() == 1); - modules.into_iter().next().unwrap().address + modules.into_iter().next().unwrap() }; - // old config to assert against - let config_v2: dao_proposal_single_v2::state::Config = app - .wrap() - .query_wasm_smart(proposal.to_string(), &QueryMsg::Config {}) - .unwrap(); app.execute_contract( - core.clone(), + sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Propose( - voting_v2::proposal::SingleChoiceProposeMsg { - title: "t".to_string(), - description: "d".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: core.to_string(), - msg: to_json_binary(&dao_interface_v2::msg::ExecuteMsg::UpdateCw20List { - to_add: vec![token.to_string()], - to_remove: vec![], - }) + &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![], - } + funds: vec![], + } .into()], - proposer: None, - }, - ), + }, &[], ) - .unwrap(); - + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Vote { + &cw_proposal_single_v1::msg::ExecuteMsg::Vote { proposal_id: 1, - vote: voting_v2::voting::Vote::Yes, - rationale: None, + vote: voting_v1::Vote::Yes, }, &[], ) - .unwrap(); + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Execute { proposal_id: 1 }, + &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 1 }, &[], ) - .unwrap(); - let tokens: Vec = app + .unwrap(); + let tokens: Vec = app .wrap() .query_wasm_smart( &core, - &dao_interface_v2::msg::QueryMsg::Cw20Balances { - start_after: None, + &cw_core_v1::msg::QueryMsg::Cw20Balances { + start_at: None, limit: None, }, ) .unwrap(); assert_eq!( tokens, - vec![dao_interface_v2::query::Cw20BalanceResponse { + vec![cw_core_v1::query::Cw20BalanceResponse { addr: token.clone(), balance: Uint128::new(100), }] @@ -276,135 +245,133 @@ fn test_v2_v3_full_migration() { app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Propose( - voting_v2::proposal::SingleChoiceProposeMsg { - 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), - }) + &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![], - } + funds: vec![], + } .into()], - proposer: None, - }, - ), + }, &[], ) - .unwrap(); + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Vote { + &cw_proposal_single_v1::msg::ExecuteMsg::Vote { proposal_id: 2, - vote: voting_v2::voting::Vote::Yes, - rationale: None, + vote: voting_v1::Vote::Yes, }, &[], ) - .unwrap(); + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Execute { proposal_id: 2 }, + &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 2 }, &[], ) - .unwrap(); - let dao_proposal_single_v2::query::ProposalResponse { - proposal: dao_proposal_single_v2::proposal::SingleChoiceProposal { status, .. }, + // 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, - &dao_proposal_single_v2::msg::QueryMsg::Proposal { proposal_id: 2 }, - ) - .unwrap(); - assert_eq!(status, voting_v2::status::Status::ExecutionFailed {}); - - // query existing proposals to assert against - let proposals_v2: dao_proposal_single_v2::query::ProposalListResponse = app - .wrap() - .query_wasm_smart( - proposal.clone(), - &dao_proposal_single_v2::msg::QueryMsg::ListProposals { - start_after: None, - limit: None, - }, + &cw_proposal_single_v1::msg::QueryMsg::Proposal { proposal_id: 2 }, ) .unwrap(); + assert_eq!(status, voting_v1::Status::Passed); // ---- - // create a proposal to migrate to v3 + // create a proposal to migrate to v2 // ---- - let v3_core_code = app.store_code(dao_dao_contract()); - let v3_proposal_code = app.store_code(proposal_single_contract()); - - // let pre_propose_info = get_pre_propose_info( - // &mut app, - // Some(UncheckedDepositInfo { - // denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, - // 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()); - // TODO test migrate with veto enabled + let pre_propose_info = get_pre_propose_info( + &mut app, + Some(UncheckedDepositInfo { + denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + amount: Uint128::new(1), + refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, + }), + false, + ); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Propose( - voting_v2::proposal::SingleChoiceProposeMsg { - title: "t".to_string(), - description: "d".to_string(), - msgs: vec![ - WasmMsg::Migrate { - contract_addr: core.to_string(), - new_code_id: v3_core_code, - msg: to_json_binary(&dao_interface::msg::MigrateMsg::FromCompatible {}) - .unwrap(), - } - .into(), - WasmMsg::Migrate { - contract_addr: proposal.to_string(), - new_code_id: v3_proposal_code, - msg: to_json_binary(&crate::msg::MigrateMsg::FromV2 { veto: None }) - .unwrap(), - } + &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(), - ], - proposer: None, - }, - ), + 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::Time(1000), + vetoer: sender.to_string(), + early_execute: true, + veto_before_passed: false, + }), + }) + .unwrap(), + } + .into(), + ], + }, &[], ) - .unwrap(); + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Vote { + &cw_proposal_single_v1::msg::ExecuteMsg::Vote { proposal_id: 3, - vote: voting_v2::voting::Vote::Yes, - rationale: None, + vote: voting_v1::Vote::Yes, }, &[], ) - .unwrap(); - + .unwrap(); app.execute_contract( sender.clone(), proposal.clone(), - &dao_proposal_single_v2::msg::ExecuteMsg::Execute { proposal_id: 3 }, + &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 3 }, &[], ) - .unwrap(); + .unwrap(); + + // ---- + // 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); // ---- // check that proposal count is still three after proposal state migration. @@ -412,6 +379,12 @@ fn test_v2_v3_full_migration() { let count = query_proposal_count(&app, &proposal); assert_eq!(count, 3); + let migrated_existing_props = query_list_proposals(&mut 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. // ---- @@ -459,10 +432,10 @@ fn test_v2_v3_full_migration() { to_add: vec![], to_remove: vec![token.into_string()], }) - .unwrap(), + .unwrap(), funds: vec![], } - .into()], + .into()], ); vote_on_proposal( &mut app, @@ -471,6 +444,15 @@ fn test_v2_v3_full_migration() { 4, dao_voting::voting::Vote::Yes, ); + + let new_prop = query_proposal(&mut app, &proposal, 4); + assert_eq!(new_prop.proposal.veto, Some(VetoConfig { + timelock_duration: Duration::Time(1000), + 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() @@ -482,79 +464,5 @@ fn test_v2_v3_full_migration() { }, ) .unwrap(); - assert!(tokens.is_empty()); - - // query the config and assert fields are properly migrated - let config: crate::state::Config = app - .wrap() - .query_wasm_smart(&proposal, &QueryMsg::Config {}) - .unwrap(); - assert_eq!(config.dao, config_v2.dao); - assert_eq!(config.allow_revoting, config_v2.allow_revoting); - assert_eq!( - to_json_binary(&config.threshold).unwrap(), - to_json_binary(&config_v2.threshold).unwrap(), - ); - assert_eq!( - config.close_proposal_on_execution_failure, - config_v2.close_proposal_on_execution_failure - ); - assert_eq!(config.max_voting_period, config_v2.max_voting_period); - assert_eq!(config.min_voting_period, config_v2.min_voting_period); - assert_eq!(config.only_members_execute, config_v2.only_members_execute); - assert_eq!(config.veto, None); - - // query migrated proposals - let proposals_v3: crate::query::ProposalListResponse = app - .wrap() - .query_wasm_smart( - proposal.to_string(), - &crate::msg::QueryMsg::ListProposals { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - // assert that all pre-migration props have been correctly migrated over - for (i, prop_v2) in proposals_v2.proposals.iter().enumerate() { - let migrated_prop = &proposals_v3.proposals[i]; - assert_eq!(prop_v2.id, migrated_prop.id); - assert_eq!(prop_v2.proposal.title, migrated_prop.proposal.title); - assert_eq!( - prop_v2.proposal.description, - migrated_prop.proposal.description - ); - assert_eq!(prop_v2.proposal.proposer, migrated_prop.proposal.proposer); - assert_eq!( - prop_v2.proposal.start_height, - migrated_prop.proposal.start_height - ); - assert_eq!( - prop_v2.proposal.min_voting_period, - migrated_prop.proposal.min_voting_period - ); - assert_eq!( - prop_v2.proposal.expiration, - migrated_prop.proposal.expiration - ); - assert_eq!( - to_json_binary(&prop_v2.proposal.threshold).unwrap(), - to_json_binary(&migrated_prop.proposal.threshold).unwrap(), - ); - assert_eq!( - prop_v2.proposal.total_power, - migrated_prop.proposal.total_power - ); - assert_eq!(prop_v2.proposal.msgs, migrated_prop.proposal.msgs); - assert_eq!( - prop_v2.proposal.status.to_string(), - migrated_prop.proposal.status.to_string() - ); - assert_eq!( - prop_v2.proposal.allow_revoting, - migrated_prop.proposal.allow_revoting - ); - assert_eq!(None, migrated_prop.proposal.veto); - } -} + assert!(tokens.is_empty()) +} \ No newline at end of file diff --git a/contracts/proposal/dao-proposal-single/src/v1_state.rs b/contracts/proposal/dao-proposal-single/src/v1_state.rs new file mode 100644 index 000000000..136ec0837 --- /dev/null +++ b/contracts/proposal/dao-proposal-single/src/v1_state.rs @@ -0,0 +1,163 @@ +//! Helper methods for migrating from v1 to v2 state. These will need +//! to be updated when we bump our CosmWasm version for v2. + +use cw_utils::{Duration, Expiration}; +use dao_voting::{ + status::Status, + threshold::{PercentageThreshold, Threshold}, + voting::Votes, +}; + +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_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_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_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_votes_to_v2(v1: voting_v1::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, + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{Decimal, Timestamp, Uint128}; + + 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_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_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_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_status_conversion() { + macro_rules! status_conversion { + ($x:expr) => { + assert_eq!( + v1_status_to_v2({ + use voting_v1::Status; + $x + }), + $x + ) + }; + } + + status_conversion!(Status::Open); + status_conversion!(Status::Closed); + status_conversion!(Status::Executed); + status_conversion!(Status::Rejected) + } +} \ No newline at end of file diff --git a/contracts/proposal/dao-proposal-single/src/v2_state.rs b/contracts/proposal/dao-proposal-single/src/v2_state.rs deleted file mode 100644 index f69d27f89..000000000 --- a/contracts/proposal/dao-proposal-single/src/v2_state.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Helper methods for migrating from v2 to v3 state. These will need -//! to be updated when we bump our CosmWasm version for v3. - -use dao_voting::{ - status::Status, - threshold::{PercentageThreshold, Threshold}, - voting::Votes, -}; - -pub fn v2_percentage_threshold_to_v3( - v2: voting_v2::threshold::PercentageThreshold, -) -> PercentageThreshold { - match v2 { - voting_v2::threshold::PercentageThreshold::Majority {} => PercentageThreshold::Majority {}, - voting_v2::threshold::PercentageThreshold::Percent(p) => PercentageThreshold::Percent(p), - } -} - -pub fn v2_threshold_to_v3(v2: voting_v2::threshold::Threshold) -> Threshold { - match v2 { - voting_v2::threshold::Threshold::AbsolutePercentage { percentage } => { - Threshold::AbsolutePercentage { - percentage: v2_percentage_threshold_to_v3(percentage), - } - } - voting_v2::threshold::Threshold::ThresholdQuorum { threshold, quorum } => { - Threshold::ThresholdQuorum { - threshold: v2_percentage_threshold_to_v3(threshold), - quorum: v2_percentage_threshold_to_v3(quorum), - } - } - voting_v2::threshold::Threshold::AbsoluteCount { threshold } => { - Threshold::AbsoluteCount { threshold } - } - } -} - -pub fn v2_votes_to_v3(v2: voting_v2::voting::Votes) -> Votes { - Votes { - yes: v2.yes, - no: v2.no, - abstain: v2.abstain, - } -} - -pub fn v2_status_to_v3(v2: voting_v2::status::Status) -> Status { - match v2 { - voting_v2::status::Status::Open => Status::Open, - voting_v2::status::Status::Rejected => Status::Rejected, - voting_v2::status::Status::Passed => Status::Passed, - voting_v2::status::Status::Executed => Status::Executed, - voting_v2::status::Status::Closed => Status::Closed, - voting_v2::status::Status::ExecutionFailed => Status::ExecutionFailed, - } -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::{Decimal, Uint128}; - - use super::*; - - #[test] - fn test_percentage_conversion() { - assert_eq!( - v2_percentage_threshold_to_v3(voting_v2::threshold::PercentageThreshold::Majority {}), - PercentageThreshold::Majority {} - ); - assert_eq!( - v2_percentage_threshold_to_v3(voting_v2::threshold::PercentageThreshold::Percent( - Decimal::percent(80) - )), - PercentageThreshold::Percent(Decimal::percent(80)) - ) - } - - #[test] - fn test_threshold_conversion() { - assert_eq!( - v2_threshold_to_v3(voting_v2::threshold::Threshold::AbsoluteCount { - threshold: Uint128::new(10) - }), - Threshold::AbsoluteCount { - threshold: Uint128::new(10) - } - ); - assert_eq!( - v2_threshold_to_v3(voting_v2::threshold::Threshold::AbsolutePercentage { - percentage: voting_v2::threshold::PercentageThreshold::Majority {} - }), - Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {} - } - ); - assert_eq!( - v2_threshold_to_v3(voting_v2::threshold::Threshold::ThresholdQuorum { - threshold: voting_v2::threshold::PercentageThreshold::Majority {}, - quorum: voting_v2::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!( - v2_status_to_v3({ - use voting_v2::status::Status; - $x - }), - $x - ) - }; - } - - status_conversion!(Status::Open); - status_conversion!(Status::Closed); - status_conversion!(Status::Executed); - status_conversion!(Status::Rejected); - status_conversion!(Status::ExecutionFailed); - status_conversion!(Status::Passed); - } -}