From 537c2c6b0af07d7e2b663c3432273ceaa8d18e8c Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Sun, 17 Dec 2023 14:03:43 -0800 Subject: [PATCH] Support using newly created token factory denom as proposal deposit during instantiation. --- ci/bootstrap-env/src/main.rs | 6 +- ci/integration-tests/src/helpers/helper.rs | 6 +- .../dao-pre-propose-approval-single.json | 34 ++++++++- .../schema/dao-pre-propose-approver.json | 17 ++++- .../schema/dao-pre-propose-multiple.json | 34 ++++++++- .../schema/dao-pre-propose-single.json | 34 ++++++++- .../src/testing/adversarial_tests.rs | 6 +- .../src/testing/instantiate.rs | 6 +- .../src/testing/tests.rs | 49 +++++++++---- .../src/testing/adversarial_tests.rs | 10 ++- .../src/testing/instantiate.rs | 6 +- .../src/testing/migration_tests.rs | 9 ++- .../dao-proposal-single/src/testing/tests.rs | 10 ++- .../test/dao-voting-cw20-balance/src/msg.rs | 4 +- .../voting/dao-voting-cw20-staked/src/msg.rs | 4 +- .../schema/dao-voting-token-staked.json | 26 +++---- .../dao-voting-token-staked/src/contract.rs | 8 ++- .../voting/dao-voting-token-staked/src/msg.rs | 10 +-- .../src/tests/multitest/tests.rs | 7 +- packages/dao-dao-macros/README.md | 4 +- packages/dao-dao-macros/src/lib.rs | 71 +++++++++++++++++-- packages/dao-interface/src/voting.rs | 8 +++ packages/dao-voting/src/deposit.rs | 57 ++++++++++----- 23 files changed, 332 insertions(+), 94 deletions(-) diff --git a/ci/bootstrap-env/src/main.rs b/ci/bootstrap-env/src/main.rs index 809b1f5a8..3a70d194c 100644 --- a/ci/bootstrap-env/src/main.rs +++ b/ci/bootstrap-env/src/main.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{to_json_binary, Decimal, Empty, Uint128}; use cw20::Cw20Coin; use dao_interface::state::{Admin, ModuleInstantiateInfo}; use dao_voting::{ - deposit::{DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, DepositToken, UncheckedDepositInfo, VotingModuleTokenType}, pre_propose::PreProposeInfo, threshold::PercentageThreshold, threshold::Threshold, @@ -93,7 +93,9 @@ fn main() -> Result<()> { code_id: orc.contract_map.code_id("dao_pre_propose_single")?, msg: to_json_binary(&dao_pre_propose_single::InstantiateMsg { deposit_info: Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1000000000), refund_policy: DepositRefundPolicy::OnlyPassed, }), diff --git a/ci/integration-tests/src/helpers/helper.rs b/ci/integration-tests/src/helpers/helper.rs index 772025e4c..8b37759d2 100644 --- a/ci/integration-tests/src/helpers/helper.rs +++ b/ci/integration-tests/src/helpers/helper.rs @@ -7,7 +7,7 @@ use cw_utils::Duration; use dao_interface::query::DumpStateResponse; use dao_interface::state::{Admin, ModuleInstantiateInfo}; use dao_voting::{ - deposit::{DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, DepositToken, UncheckedDepositInfo, VotingModuleTokenType}, pre_propose::{PreProposeInfo, ProposalCreationPolicy}, threshold::PercentageThreshold, threshold::Threshold, @@ -78,7 +78,9 @@ pub fn create_dao( code_id: chain.orc.contract_map.code_id("dao_pre_propose_single")?, msg: to_json_binary(&dao_pre_propose_single::InstantiateMsg { deposit_info: Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: DEPOSIT_AMOUNT, refund_policy: DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json index f748e5ee3..e6f0627f3 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json +++ b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json @@ -88,7 +88,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -96,6 +96,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -185,6 +193,13 @@ } }, "additionalProperties": false + }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] } } }, @@ -609,7 +624,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -617,6 +632,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -1275,6 +1298,13 @@ "no_with_veto" ] }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] + }, "WasmMsg": { "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", "oneOf": [ diff --git a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json index b0b52bbce..ed5ee1c1f 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json +++ b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json @@ -283,7 +283,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -291,6 +291,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -519,6 +527,13 @@ } }, "additionalProperties": false + }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] } } }, diff --git a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json index 6faed06ed..e85597f5a 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json @@ -88,7 +88,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -96,6 +96,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -177,6 +185,13 @@ } }, "additionalProperties": false + }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] } } }, @@ -601,7 +616,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -609,6 +624,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -1230,6 +1253,13 @@ "no_with_veto" ] }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] + }, "WasmMsg": { "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", "oneOf": [ diff --git a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json index b9a943395..5d5c772ca 100644 --- a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json +++ b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json @@ -88,7 +88,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -96,6 +96,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -177,6 +185,13 @@ } }, "additionalProperties": false + }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] } } }, @@ -601,7 +616,7 @@ "additionalProperties": false }, { - "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `dao_dao_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "description": "Use the token native denom or cw20 contract address of the associated DAO's voting module. NOTE: in order to retrieve the token automatically via this variant, the voting module must either (1) use a native token and implement the `Denom {}` query type defined by `dao_dao_macros::native_token_query` OR (2) use a cw20 token and implement the `TokenContract {}` query type defined by `dao_dao_macros::cw20_token_query`. Failing to implement correctly will cause this option to fail to instantiate.", "type": "object", "required": [ "voting_module_token" @@ -609,6 +624,14 @@ "properties": { "voting_module_token": { "type": "object", + "required": [ + "token_type" + ], + "properties": { + "token_type": { + "$ref": "#/definitions/VotingModuleTokenType" + } + }, "additionalProperties": false } }, @@ -1194,6 +1217,13 @@ "no_with_veto" ] }, + "VotingModuleTokenType": { + "type": "string", + "enum": [ + "native", + "cw20" + ] + }, "WasmMsg": { "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", "oneOf": [ diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs index 3c27c98d1..d85499917 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs @@ -14,7 +14,7 @@ use cw20::Cw20Coin; use cw_multi_test::{next_block, App, Executor}; use cw_utils::Duration; use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, multiple_choice::{ MultipleChoiceOption, MultipleChoiceOptions, MultipleChoiceVote, VotingStrategy, }, @@ -273,7 +273,9 @@ pub fn test_allow_voting_after_proposal_execution_pre_expiration_cw20() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(10_000_000), refund_policy: DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index bd954913b..e126b3d8e 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -10,7 +10,7 @@ use dao_testing::contracts::{ dao_dao_contract, native_staked_balances_voting_contract, pre_propose_multiple_contract, }; use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, multiple_choice::VotingStrategy, pre_propose::PreProposeInfo, threshold::{ActiveThreshold, ActiveThreshold::AbsoluteCount, PercentageThreshold}, @@ -58,7 +58,9 @@ pub fn _get_default_token_dao_proposal_module_instantiate(app: &mut App) -> Inst pre_propose_info: get_pre_propose_info( app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(10_000_000), refund_policy: DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index ce8dea003..4b97622db 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -10,7 +10,10 @@ use dao_interface::state::ProposalModule; use dao_interface::state::{Admin, ModuleInstantiateInfo}; use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::{ - deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, + deposit::{ + CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo, + VotingModuleTokenType, + }, multiple_choice::{ CheckedMultipleChoiceOption, MultipleChoiceOption, MultipleChoiceOptionType, MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, @@ -715,7 +718,9 @@ fn test_voting_module_token_proposal_deposit_instantiate() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -884,7 +889,9 @@ fn test_take_proposal_deposit() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -1131,7 +1138,9 @@ fn test_deposit_return_on_execute() { Status::Passed, None, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -1416,7 +1425,9 @@ fn test_cant_propose_zero_power() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::Always, }), @@ -1532,7 +1543,9 @@ fn test_cant_vote_not_registered() { Status::Open, Some(Uint128::new(100)), Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::Always, }), @@ -1887,7 +1900,9 @@ fn test_close_open_proposal() { Status::Open, Some(Uint128::new(100)), Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::Always, }), @@ -1955,7 +1970,9 @@ fn test_no_refund_failed_proposal() { Status::Open, Some(Uint128::new(100)), Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -2033,7 +2050,9 @@ fn test_deposit_return_on_close() { Status::Rejected, None, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::Always, }), @@ -2333,7 +2352,9 @@ fn test_no_return_if_no_refunds() { Status::Rejected, None, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -3529,7 +3550,9 @@ fn test_return_deposit_to_dao_on_proposal_failure() { Status::Open, Some(Uint128::new(100)), Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -3832,7 +3855,9 @@ fn test_no_double_refund_on_execute_fail_and_close() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken {}, + denom: DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), // Important to set to true here as we want to be sure // that we don't get a second refund on close. Refunds on 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 c5e419d9b..b8883e933 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -16,7 +16,7 @@ use cw20::Cw20Coin; use cw_multi_test::{next_block, App}; use cw_utils::Duration; use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, status::Status, threshold::{PercentageThreshold, Threshold::AbsolutePercentage}, voting::Vote, @@ -171,7 +171,9 @@ pub fn test_executed_prop_state_remains_after_vote_swing() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(10_000_000), refund_policy: DepositRefundPolicy::OnlyPassed, }), @@ -268,7 +270,9 @@ pub fn test_passed_prop_state_remains_after_vote_swing() { pre_propose_info: get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(10_000_000), refund_policy: DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 14b84d3e2..020154700 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -7,7 +7,7 @@ use dao_interface::state::{Admin, ModuleInstantiateInfo}; use dao_pre_propose_single as cppbps; use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo}, + deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, pre_propose::PreProposeInfo, threshold::{ActiveThreshold, PercentageThreshold, Threshold::ThresholdQuorum}, }; @@ -61,7 +61,9 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate(app: &mut App) - pre_propose_info: get_pre_propose_info( app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(10_000_000), refund_policy: DepositRefundPolicy::OnlyPassed, }), 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 3dc5c33e8..8dab11460 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs @@ -8,7 +8,10 @@ use dao_testing::contracts::{ dao_dao_contract, proposal_single_contract, v1_dao_dao_contract, v1_proposal_single_contract, }; use dao_voting::veto::VetoConfig; -use dao_voting::{deposit::UncheckedDepositInfo, status::Status}; +use dao_voting::{ + deposit::{UncheckedDepositInfo, VotingModuleTokenType}, + status::Status, +}; use crate::testing::queries::query_list_proposals; use crate::testing::{ @@ -303,7 +306,9 @@ fn test_v1_v2_full_migration() { let pre_propose_info = get_pre_propose_info( &mut app, Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index 78c5821b4..404dc042d 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -18,7 +18,7 @@ use dao_interface::{ }; use dao_testing::{ShouldExecute, TestSingleChoiceVote}; use dao_voting::{ - deposit::{CheckedDepositInfo, UncheckedDepositInfo}, + deposit::{CheckedDepositInfo, UncheckedDepositInfo, VotingModuleTokenType}, pre_propose::{PreProposeInfo, ProposalCreationPolicy}, proposal::{SingleChoiceProposeMsg as ProposeMsg, MAX_PROPOSAL_SIZE}, reply::{ @@ -2995,7 +2995,9 @@ pub fn test_migrate_updates_version() { // only_members_execute: false, // allow_revoting: false, // deposit_info: Some(v1::msg::DepositInfo { -// token: v1::msg::DepositToken::VotingModuleToken {}, +// token: v1::msg::DepositToken::VotingModuleToken { +// token_type: VotingModuleTokenType::Cw20, +// }, // deposit: Uint128::new(1), // refund_failed_proposals: true, // }), @@ -3798,7 +3800,9 @@ fn test_update_pre_propose_module() { code_id: pre_propose_id, msg: to_json_binary(&dao_pre_propose_single::InstantiateMsg { deposit_info: Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken {}, + denom: dao_voting::deposit::DepositToken::VotingModuleToken { + token_type: VotingModuleTokenType::Cw20, + }, amount: Uint128::new(1), refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, }), diff --git a/contracts/test/dao-voting-cw20-balance/src/msg.rs b/contracts/test/dao-voting-cw20-balance/src/msg.rs index 6a0a5cae3..c41590ee2 100644 --- a/contracts/test/dao-voting-cw20-balance/src/msg.rs +++ b/contracts/test/dao-voting-cw20-balance/src/msg.rs @@ -3,7 +3,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cw20::Cw20Coin; use cw20_base::msg::InstantiateMarketingInfo; -use dao_dao_macros::{token_query, voting_module_query}; +use dao_dao_macros::{cw20_token_query, voting_module_query}; #[cw_serde] pub enum TokenInfo { @@ -30,7 +30,7 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg {} -#[token_query] +#[cw20_token_query] #[voting_module_query] #[cw_serde] #[derive(QueryResponses)] diff --git a/contracts/voting/dao-voting-cw20-staked/src/msg.rs b/contracts/voting/dao-voting-cw20-staked/src/msg.rs index 33753fcae..bdb5e9f2a 100644 --- a/contracts/voting/dao-voting-cw20-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw20-staked/src/msg.rs @@ -4,7 +4,7 @@ use cw20::Cw20Coin; use cw20_base::msg::InstantiateMarketingInfo; use cw_utils::Duration; -use dao_dao_macros::{active_query, token_query, voting_module_query}; +use dao_dao_macros::{active_query, cw20_token_query, voting_module_query}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; /// Information about the staking contract to be used with this voting @@ -71,7 +71,7 @@ pub enum ExecuteMsg { } #[voting_module_query] -#[token_query] +#[cw20_token_query] #[active_query] #[cw_serde] #[derive(QueryResponses)] diff --git a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json index 281393190..12cfdea3a 100644 --- a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json @@ -587,19 +587,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -689,6 +676,19 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ diff --git a/contracts/voting/dao-voting-token-staked/src/contract.rs b/contracts/voting/dao-voting-token-staked/src/contract.rs index ffcba6902..15f19fb6e 100644 --- a/contracts/voting/dao-voting-token-staked/src/contract.rs +++ b/contracts/voting/dao-voting-token-staked/src/contract.rs @@ -18,7 +18,9 @@ use dao_hooks::stake::{stake_hook_msgs, unstake_hook_msgs}; use dao_interface::{ state::ModuleInstantiateCallback, token::{InitialBalance, NewTokenInfo, TokenFactoryCallback}, - voting::{IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse}, + voting::{ + DenomResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, + }, }; use dao_voting::{ duration::validate_duration, @@ -30,8 +32,8 @@ use dao_voting::{ use crate::error::ContractError; use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, TokenInfo, + ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, + StakerBalanceResponse, TokenInfo, }; use crate::state::{ Config, ACTIVE_THRESHOLD, CLAIMS, CONFIG, DAO, DENOM, HOOKS, MAX_CLAIMS, STAKED_BALANCES, diff --git a/contracts/voting/dao-voting-token-staked/src/msg.rs b/contracts/voting/dao-voting-token-staked/src/msg.rs index 52c71baa9..98809ec9e 100644 --- a/contracts/voting/dao-voting-token-staked/src/msg.rs +++ b/contracts/voting/dao-voting-token-staked/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Binary, Uint128}; use cw_utils::Duration; -use dao_dao_macros::{active_query, voting_module_query}; +use dao_dao_macros::{active_query, native_token_query, voting_module_query}; use dao_interface::token::NewTokenInfo; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -57,6 +57,7 @@ pub enum ExecuteMsg { RemoveHook { addr: String }, } +#[native_token_query] #[active_query] #[voting_module_query] #[cw_serde] @@ -64,8 +65,6 @@ pub enum ExecuteMsg { pub enum QueryMsg { #[returns(crate::state::Config)] GetConfig {}, - #[returns(DenomResponse)] - Denom {}, #[returns(cw_controllers::ClaimsResponse)] Claims { address: String }, #[returns(ListStakersResponse)] @@ -95,11 +94,6 @@ pub struct StakerBalanceResponse { pub balance: Uint128, } -#[cw_serde] -pub struct DenomResponse { - pub denom: String, -} - #[cw_serde] pub struct GetHooksResponse { pub hooks: Vec, diff --git a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs index 3498764ec..90b4fab28 100644 --- a/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs +++ b/contracts/voting/dao-voting-token-staked/src/tests/multitest/tests.rs @@ -1,7 +1,7 @@ use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; use crate::msg::{ - DenomResponse, ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, - QueryMsg, StakerBalanceResponse, TokenInfo, + ExecuteMsg, GetHooksResponse, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, + StakerBalanceResponse, TokenInfo, }; use crate::state::Config; use cosmwasm_std::testing::{mock_dependencies, mock_env}; @@ -12,7 +12,8 @@ use cw_multi_test::{ }; use cw_utils::Duration; use dao_interface::voting::{ - InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, + DenomResponse, InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, + VotingPowerAtHeightResponse, }; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; diff --git a/packages/dao-dao-macros/README.md b/packages/dao-dao-macros/README.md index 404f8acda..d65999b77 100644 --- a/packages/dao-dao-macros/README.md +++ b/packages/dao-dao-macros/README.md @@ -6,11 +6,11 @@ the voting module interface on an enum: ```rust use cosmwasm_schema::{cw_serde, QueryResponses}; -use dao_dao_macros::{token_query, voting_module_query}; +use dao_dao_macros::{cw20_token_query, voting_module_query}; use dao_interface::voting::TotalPowerAtHeightResponse; use dao_interface::voting::VotingPowerAtHeightResponse; -#[token_query] +#[cw20_token_query] #[voting_module_query] #[cw_serde] #[derive(QueryResponses)] diff --git a/packages/dao-dao-macros/src/lib.rs b/packages/dao-dao-macros/src/lib.rs index 8b2dddda9..48611fddf 100644 --- a/packages/dao-dao-macros/src/lib.rs +++ b/packages/dao-dao-macros/src/lib.rs @@ -152,16 +152,16 @@ pub fn voting_module_query(metadata: TokenStream, input: TokenStream) -> TokenSt } /// Adds the necessary fields to an enum such that it implements the -/// interface needed to be a voting module with a token. +/// interface needed to be a voting module with a cw20 token. /// /// For example: /// /// ``` -/// use dao_dao_macros::token_query; +/// use dao_dao_macros::cw20_token_query; /// use cosmwasm_schema::{cw_serde, QueryResponses}; /// use cosmwasm_std::Addr; /// -/// #[token_query] +/// #[cw20_token_query] /// #[cw_serde] /// #[derive(QueryResponses)] /// enum QueryMsg {} @@ -181,11 +181,11 @@ pub fn voting_module_query(metadata: TokenStream, input: TokenStream) -> TokenSt /// occurs before the addition of the field. /// /// ```compile_fail -/// use dao_dao_macros::token_query; +/// use dao_dao_macros::cw20_token_query; /// use cosmwasm_schema::{cw_serde, QueryResponses}; /// /// #[derive(Clone)] -/// #[token_query] +/// #[cw20_token_query] /// #[allow(dead_code)] /// #[cw_serde] /// #[derive(QueryResponses)] @@ -196,7 +196,7 @@ pub fn voting_module_query(metadata: TokenStream, input: TokenStream) -> TokenSt /// } /// ``` #[proc_macro_attribute] -pub fn token_query(metadata: TokenStream, input: TokenStream) -> TokenStream { +pub fn cw20_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream { merge_variants( metadata, input, @@ -210,6 +210,65 @@ pub fn token_query(metadata: TokenStream, input: TokenStream) -> TokenStream { ) } +/// Adds the necessary fields to an enum such that it implements the +/// interface needed to be a voting module with a native token. +/// +/// For example: +/// +/// ``` +/// use dao_dao_macros::native_token_query; +/// use cosmwasm_schema::{cw_serde, QueryResponses}; +/// use cosmwasm_std::Addr; +/// +/// #[native_token_query] +/// #[cw_serde] +/// #[derive(QueryResponses)] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// Denom {}, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use dao_dao_macros::native_token_query; +/// use cosmwasm_schema::{cw_serde, QueryResponses}; +/// +/// #[derive(Clone)] +/// #[native_token_query] +/// #[allow(dead_code)] +/// #[cw_serde] +/// #[derive(QueryResponses)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn native_token_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote! { + enum Right { + #[returns(::dao_interface::voting::DenomResponse)] + Denom {} + } + } + .into(), + ) +} + /// Adds the necessary fields to an enum such that it implements the /// interface needed to be a voting module that has an /// active check threshold. diff --git a/packages/dao-interface/src/voting.rs b/packages/dao-interface/src/voting.rs index cf6d43707..3ef95b971 100644 --- a/packages/dao-interface/src/voting.rs +++ b/packages/dao-interface/src/voting.rs @@ -8,6 +8,9 @@ pub enum Query { /// Returns the token contract address, if set. #[returns(::cosmwasm_std::Addr)] TokenContract {}, + /// Returns the native token denom, if used. + #[returns(DenomResponse)] + Denom {}, /// Returns the voting power for an address at a given height. #[returns(VotingPowerAtHeightResponse)] VotingPowerAtHeight { @@ -56,3 +59,8 @@ pub struct InfoResponse { pub struct IsActiveResponse { pub active: bool, } + +#[cw_serde] +pub struct DenomResponse { + pub denom: String, +} diff --git a/packages/dao-voting/src/deposit.rs b/packages/dao-voting/src/deposit.rs index 33fbb832c..17f23418e 100644 --- a/packages/dao-voting/src/deposit.rs +++ b/packages/dao-voting/src/deposit.rs @@ -4,6 +4,7 @@ use cosmwasm_std::{ }; use cw_utils::{must_pay, PaymentError}; +use dao_interface::voting::DenomResponse; use thiserror::Error; use cw_denom::{CheckedDenom, DenomError, UncheckedDenom}; @@ -27,18 +28,27 @@ pub enum DepositError { InvalidDeposit { actual: Uint128, expected: Uint128 }, } +// The voting module token type to expect. +#[cw_serde] +pub enum VotingModuleTokenType { + Native, + Cw20, +} + /// Information about the token to use for proposal deposits. #[cw_serde] pub enum DepositToken { /// Use a specific token address as the deposit token. Token { denom: UncheckedDenom }, - /// Use the token address of the associated DAO's voting - /// module. NOTE: in order to use the token address of the voting - /// module the voting module must (1) use a cw20 token and (2) + /// Use the token native denom or cw20 contract address of the associated + /// DAO's voting module. NOTE: in order to retrieve the token automatically + /// via this variant, the voting module must either (1) use a native token + /// and implement the `Denom {}` query type defined by + /// `dao_dao_macros::native_token_query` OR (2) use a cw20 token and /// implement the `TokenContract {}` query type defined by - /// `dao_dao_macros::token_query`. Failing to implement that - /// and using this option will cause instantiation to fail. - VotingModuleToken {}, + /// `dao_dao_macros::cw20_token_query`. Failing to implement correctly will + /// cause this option to fail to instantiate. + VotingModuleToken { token_type: VotingModuleTokenType }, } /// Information about the deposit required to create a proposal. @@ -98,21 +108,32 @@ impl UncheckedDepositInfo { let denom = match denom { DepositToken::Token { denom } => denom.into_checked(deps), - DepositToken::VotingModuleToken {} => { + DepositToken::VotingModuleToken { token_type } => { let voting_module: Addr = deps .querier .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {})?; - // If the voting module has no token this will - // error. This is desirable. - let token_addr: Addr = deps.querier.query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - )?; - // We don't assume here that the voting module has - // returned a valid token. Conversion of the unchecked - // denom into a checked one will do a `TokenInfo {}` - // query. - UncheckedDenom::Cw20(token_addr.into_string()).into_checked(deps) + + if token_type == VotingModuleTokenType::Native { + // If the voting module has no native token denom this will + // error. This is desirable. + let denom: DenomResponse = deps + .querier + .query_wasm_smart(voting_module, &dao_interface::voting::Query::Denom {})?; + // Validate that native denom is formatted correctly. + UncheckedDenom::Native(denom.denom).into_checked(deps) + } else { + // If the voting module has no cw20 token this will error. + // This is desirable. + let token_addr: Addr = deps.querier.query_wasm_smart( + voting_module, + &dao_interface::voting::Query::TokenContract {}, + )?; + // We don't assume here that the voting module has + // returned a valid token. Conversion of the unchecked + // denom into a checked one will do a `TokenInfo {}` + // query. + UncheckedDenom::Cw20(token_addr.into_string()).into_checked(deps) + } } }?;