From 6b0aa22d09d040569cf604aba1ff6162ebc3877d Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Sat, 2 Dec 2023 14:43:35 -0800 Subject: [PATCH] Added veto to dao-proposal-multiple. --- .../dao-pre-propose-multiple/src/tests.rs | 3 + .../schema/dao-proposal-multiple.json | 474 +++++++++++++++++- .../dao-proposal-multiple/src/contract.rs | 194 +++++-- .../dao-proposal-multiple/src/error.rs | 7 +- .../proposal/dao-proposal-multiple/src/msg.rs | 20 + .../dao-proposal-multiple/src/proposal.rs | 40 +- .../dao-proposal-multiple/src/state.rs | 4 + .../src/testing/adversarial_tests.rs | 1 + .../src/testing/do_votes.rs | 1 + .../src/testing/instantiate.rs | 2 + .../src/testing/tests.rs | 47 ++ 11 files changed, 727 insertions(+), 66 deletions(-) diff --git a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs index 241bb039f..4a8825a5c 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs +++ b/contracts/pre-propose/dao-pre-propose-multiple/src/tests.rs @@ -77,6 +77,7 @@ fn get_default_proposal_module_instantiate( }, }, close_proposal_on_execution_failure: false, + veto: None, } } @@ -1065,6 +1066,7 @@ fn test_instantiate_with_zero_native_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; @@ -1127,6 +1129,7 @@ fn test_instantiate_with_zero_cw20_deposit() { }, }, close_proposal_on_execution_failure: false, + veto: None, } }; diff --git a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json index f941b683e..1c0449113 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -54,6 +54,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration for proposal execution. If set, proposals can only be executed after the timelock delay expiration. During this period an oversight account (`veto.vetoer`) can veto the proposal.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "Voting params configuration", "allOf": [ @@ -288,6 +299,38 @@ "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VotingStrategy": { "description": "Determines how many choices may be selected.", "oneOf": [ @@ -430,6 +473,31 @@ }, "additionalProperties": false }, + { + "description": "Callable only if veto is configured", + "type": "object", + "required": [ + "veto" + ], + "properties": { + "veto": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to veto.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Closes a proposal that has failed (either not passed or timed out). If applicable this will cause the proposal deposit associated wth said proposal to be returned.", "type": "object", @@ -508,6 +576,17 @@ "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal. Applies to all outstanding and future proposals.", "type": "boolean" }, + "veto": { + "description": "Optional time delay on proposal execution, during which the proposal may be vetoed.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "The new proposal voting strategy. This will only apply to proposals created after the config update.", "allOf": [ @@ -1447,6 +1526,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -1959,6 +2070,17 @@ "$ref": "#/definitions/PreProposeInfo" } ] + }, + "veto": { + "description": "This field was not present in DAO DAO v1. To migrate, a value must be specified.\n\noptional configuration for veto feature", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -2041,6 +2163,40 @@ } } }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "ModuleInstantiateInfo": { "description": "Information needed to instantiate a module.", "type": "object", @@ -2133,6 +2289,38 @@ "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" + }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -2191,6 +2379,17 @@ "description": "If set to true only members may execute passed proposals. Otherwise, any address may execute a passed proposal.", "type": "boolean" }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "voting_strategy": { "description": "The threshold a proposal must reach to complete.", "allOf": [ @@ -2276,6 +2475,38 @@ } ] }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VotingStrategy": { "description": "Determines how many choices may be selected.", "oneOf": [ @@ -2731,6 +2962,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -3018,6 +3283,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -3054,7 +3320,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -3062,6 +3328,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -3072,6 +3339,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -3332,6 +3610,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -3941,6 +4251,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -4228,6 +4572,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -4264,7 +4609,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -4272,6 +4617,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -4282,6 +4628,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -4523,6 +4880,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ @@ -5108,6 +5497,40 @@ } ] }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, "Empty": { "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", "type": "object" @@ -5395,6 +5818,7 @@ } }, "description": { + "description": "The main body of the proposal text", "type": "string" }, "expiration": { @@ -5431,7 +5855,7 @@ "minimum": 0.0 }, "status": { - "description": "Prosal status (Open, rejected, executed, execution failed, closed, passed)", + "description": "The proposal status", "allOf": [ { "$ref": "#/definitions/Status" @@ -5439,6 +5863,7 @@ ] }, "title": { + "description": "The title of the proposal", "type": "string" }, "total_power": { @@ -5449,6 +5874,17 @@ } ] }, + "veto": { + "description": "Optional veto configuration. If set to `None`, veto option is disabled. Otherwise contains the configuration for veto flow.", + "anyOf": [ + { + "$ref": "#/definitions/VetoConfig" + }, + { + "type": "null" + } + ] + }, "votes": { "description": "The vote tally.", "allOf": [ @@ -5709,6 +6145,38 @@ "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" }, + "VetoConfig": { + "type": "object", + "required": [ + "early_execute", + "timelock_duration", + "veto_before_passed", + "vetoer" + ], + "properties": { + "early_execute": { + "description": "Whether or not the vetoer can execute a proposal early before the timelock duration has expired", + "type": "boolean" + }, + "timelock_duration": { + "description": "The time duration to delay proposal execution for.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "veto_before_passed": { + "description": "Whether or not the vetoer can veto a proposal before it passes.", + "type": "boolean" + }, + "vetoer": { + "description": "The address able to veto proposals.", + "type": "string" + } + }, + "additionalProperties": false + }, "VoteOption": { "type": "string", "enum": [ diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index c1af47ced..8f8c7504e 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Order, Reply, Response, StdResult, Storage, SubMsg, WasmMsg, }; @@ -9,10 +9,12 @@ use cw2::set_contract_version; use cw_hooks::Hooks; use cw_storage_plus::Bound; use cw_utils::{parse_reply_instantiate_data, Duration}; -use dao_hooks::proposal::{new_proposal_hooks, proposal_status_changed_hooks}; +use dao_hooks::proposal::{ + new_proposal_hooks, proposal_completed_hooks, proposal_status_changed_hooks, +}; use dao_hooks::vote::new_vote_hooks; use dao_interface::voting::IsActiveResponse; -use dao_pre_propose_multiple::contract::ExecuteMsg as PreProposeMsg; +use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::{ multiple_choice::{ MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, @@ -68,6 +70,7 @@ pub fn instantiate( allow_revoting: msg.allow_revoting, dao, close_proposal_on_execution_failure: msg.close_proposal_on_execution_failure, + veto: msg.veto, }; // Initialize proposal count to zero so that queries return zero @@ -110,6 +113,7 @@ pub fn execute( rationale, } => execute_vote(deps, env, info, proposal_id, vote, rationale), ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id), + ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), ExecuteMsg::UpdateConfig { voting_strategy, @@ -119,6 +123,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, } => execute_update_config( deps, info, @@ -129,6 +134,7 @@ pub fn execute( allow_revoting, dao, close_proposal_on_execution_failure, + veto, ), ExecuteMsg::UpdatePreProposeInfo { info: new_info } => { execute_update_proposal_creation_policy(deps, info, new_info) @@ -217,6 +223,7 @@ pub fn execute_propose( votes: MultipleChoiceVotes::zero(checked_multiple_choice_options.len()), allow_revoting: config.allow_revoting, choices: checked_multiple_choice_options, + veto: config.veto, }; // Update the proposal's status. Addresses case where proposal // expires on the same block as it is created. @@ -259,6 +266,79 @@ pub fn execute_propose( .add_attribute("status", proposal.status.to_string())) } +pub fn execute_veto( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result { + let mut prop = PROPOSALS + .may_load(deps.storage, proposal_id)? + .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; + + // ensure status is up to date + prop.update_status(&env.block)?; + let old_status = prop.status; + + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check sender is vetoer + veto_config.check_is_vetoer(&info)?; + + match prop.status { + Status::Open => { + // can only veto an open proposal if veto_before_passed is enabled. + veto_config.check_veto_before_passed_enabled()?; + } + Status::Passed => { + // if this proposal has veto configured but is in the passed state, + // the timelock already expired, so provide a more specific error. + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + Status::VetoTimelock { expiration } => { + // vetoer can veto the proposal iff the timelock is active/not + // expired. this should never happen since the status updates to + // passed after the timelock expires, but let's check anyway. + if expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::TimelockExpired {})); + } + } + // generic status error if the proposal has any other status. + _ => { + return Err(ContractError::VetoError(VetoError::InvalidProposalStatus { + status: prop.status.to_string(), + })); + } + } + + // Update proposal status to vetoed + prop.status = Status::Vetoed; + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + // Add proposal status change hooks + let proposal_status_changed_hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + prop.status.to_string(), + )?; + + // Add prepropose / deposit module hook which will handle deposit refunds. + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; + + Ok(Response::new() + .add_attribute("action", "veto") + .add_attribute("proposal_id", proposal_id.to_string()) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks)) +} + pub fn execute_vote( deps: DepsMut, env: Env, @@ -375,18 +455,52 @@ pub fn execute_execute( &config.dao, Some(prop.start_height), )?; - if power.is_zero() { + + // if there is no veto config, then caller is not the vetoer + // if there is, we validate the caller addr + let vetoer_call = prop + .veto + .as_ref() + .map_or(false, |veto_config| veto_config.vetoer == info.sender); + + if power.is_zero() && !vetoer_call { return Err(ContractError::Unauthorized {}); } } - // Check here that the proposal is passed. Allow it to be - // executed even if it is expired so long as it passed during its - // voting period. + // Check here that the proposal is passed or timelocked. + // Allow it to be executed even if it is expired so long + // as it passed during its voting period. Allow it to be + // executed in timelock state if early_execute is enabled + // and the sender is the vetoer. prop.update_status(&env.block)?; let old_status = prop.status; - if prop.status != Status::Passed { - return Err(ContractError::NotPassed {}); + match &prop.status { + Status::Passed => (), + Status::VetoTimelock { expiration } => { + let veto_config = prop + .veto + .as_ref() + .ok_or(VetoError::NoVetoConfiguration {})?; + + // Check if the sender is the vetoer + match veto_config.vetoer == info.sender { + // if sender is the vetoer we validate the early exec flag + true => veto_config.check_early_execute_enabled()?, + // otherwise timelock must be expired in order to execute + false => { + // it should never be expired here since the status updates + // to passed after the timelock expires, but let's check + // anyway. i.e. this error should always be returned. + if !expiration.is_expired(&env.block) { + return Err(ContractError::VetoError(VetoError::Timelocked {})); + } + } + } + } + _ => { + return Err(ContractError::NotPassed {}); + } } prop.status = Status::Executed; @@ -419,7 +533,7 @@ pub fn execute_execute( Response::default() }; - let hooks = proposal_status_changed_hooks( + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -429,28 +543,12 @@ pub fn execute_execute( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; Ok(response - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "execute") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string()) @@ -478,7 +576,7 @@ pub fn execute_close( PROPOSALS.save(deps.storage, proposal_id, &prop)?; - let hooks = proposal_status_changed_hooks( + let proposal_status_changed_hooks = proposal_status_changed_hooks( PROPOSAL_HOOKS, deps.storage, proposal_id, @@ -488,27 +586,12 @@ pub fn execute_close( // Add prepropose / deposit module hook which will handle deposit refunds. let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; - let hooks = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => hooks, - ProposalCreationPolicy::Module { addr } => { - let msg = to_json_binary(&PreProposeMsg::ProposalCompletedHook { - proposal_id, - new_status: prop.status, - })?; - let mut hooks = hooks; - hooks.push(SubMsg::reply_on_error( - WasmMsg::Execute { - contract_addr: addr.into_string(), - msg, - funds: vec![], - }, - failed_pre_propose_module_hook_id(), - )); - hooks - } - }; + let proposal_completed_hooks = + proposal_completed_hooks(proposal_creation_policy, proposal_id, prop.status)?; + Ok(Response::default() - .add_submessages(hooks) + .add_submessages(proposal_status_changed_hooks) + .add_submessages(proposal_completed_hooks) .add_attribute("action", "close") .add_attribute("sender", info.sender) .add_attribute("proposal_id", proposal_id.to_string())) @@ -525,6 +608,7 @@ pub fn execute_update_config( allow_revoting: bool, dao: String, close_proposal_on_execution_failure: bool, + veto: Option, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -540,6 +624,11 @@ pub fn execute_update_config( let (min_voting_period, max_voting_period) = validate_voting_period(min_voting_period, max_voting_period)?; + if let Some(ref veto_config) = veto { + // If veto is enabled, validate the vetoer address + deps.api.addr_validate(&veto_config.vetoer)?; + } + CONFIG.save( deps.storage, &Config { @@ -550,6 +639,7 @@ pub fn execute_update_config( allow_revoting, dao, close_proposal_on_execution_failure, + veto, }, )?; @@ -845,7 +935,7 @@ pub fn query_list_votes( let votes = BALLOTS .prefix(proposal_id) - .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) + .range(deps.storage, min, None, Order::Ascending) .take(limit as usize) .map(|item| { let (voter, ballot) = item?; diff --git a/contracts/proposal/dao-proposal-multiple/src/error.rs b/contracts/proposal/dao-proposal-multiple/src/error.rs index a1d1df105..76fe05724 100644 --- a/contracts/proposal/dao-proposal-multiple/src/error.rs +++ b/contracts/proposal/dao-proposal-multiple/src/error.rs @@ -3,10 +3,10 @@ use std::u64; use cosmwasm_std::StdError; use cw_hooks::HookError; use cw_utils::ParseReplyError; -use dao_voting::{reply::error::TagError, threshold::ThresholdError}; +use dao_voting::{reply::error::TagError, threshold::ThresholdError, veto::VetoError}; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -17,6 +17,9 @@ pub enum ContractError { #[error("{0}")] HookError(#[from] HookError), + #[error(transparent)] + VetoError(#[from] VetoError), + #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/proposal/dao-proposal-multiple/src/msg.rs b/contracts/proposal/dao-proposal-multiple/src/msg.rs index a79fca805..482ffadd7 100644 --- a/contracts/proposal/dao-proposal-multiple/src/msg.rs +++ b/contracts/proposal/dao-proposal-multiple/src/msg.rs @@ -4,6 +4,7 @@ use dao_dao_macros::proposal_module_query; use dao_voting::{ multiple_choice::{MultipleChoiceOptions, MultipleChoiceVote, VotingStrategy}, pre_propose::PreProposeInfo, + veto::VetoConfig, }; #[cw_serde] @@ -37,6 +38,12 @@ pub struct InstantiateMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration for proposal execution. + /// If set, proposals can only be executed after the timelock + /// delay expiration. + /// During this period an oversight account (`veto.vetoer`) can + /// veto the proposal. + pub veto: Option, } #[cw_serde] @@ -74,6 +81,11 @@ pub enum ExecuteMsg { /// The ID of the proposal to execute. proposal_id: u64, }, + /// Callable only if veto is configured + Veto { + /// The ID of the proposal to veto. + proposal_id: u64, + }, /// Closes a proposal that has failed (either not passed or timed /// out). If applicable this will cause the proposal deposit /// associated wth said proposal to be returned. @@ -116,6 +128,9 @@ pub enum ExecuteMsg { /// remain open until the DAO's treasury was large enough for it to be /// executed. close_proposal_on_execution_failure: bool, + /// Optional time delay on proposal execution, during which the + /// proposal may be vetoed. + veto: Option, }, /// Updates the sender's rationale for their vote on the specified /// proposal. Errors if no vote vote has been cast. @@ -217,6 +232,11 @@ pub enum MigrateMsg { /// 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 v1. To migrate, a + /// value must be specified. + /// + /// optional configuration for veto feature + veto: Option, }, FromCompatible {}, } diff --git a/contracts/proposal/dao-proposal-multiple/src/proposal.rs b/contracts/proposal/dao-proposal-multiple/src/proposal.rs index 4852993db..72557c55c 100644 --- a/contracts/proposal/dao-proposal-multiple/src/proposal.rs +++ b/contracts/proposal/dao-proposal-multiple/src/proposal.rs @@ -6,6 +6,7 @@ use dao_voting::{ CheckedMultipleChoiceOption, MultipleChoiceOptionType, MultipleChoiceVotes, VotingStrategy, }, status::Status, + veto::VetoConfig, voting::does_vote_count_pass, }; @@ -13,7 +14,9 @@ use crate::query::ProposalResponse; #[cw_serde] pub struct MultipleChoiceProposal { + /// The title of the proposal pub title: String, + /// The main body of the proposal text pub description: String, /// The address that created this proposal. pub proposer: Addr, @@ -30,7 +33,7 @@ pub struct MultipleChoiceProposal { pub expiration: Expiration, /// The options to be chosen from in the vote. pub choices: Vec, - /// Prosal status (Open, rejected, executed, execution failed, closed, passed) + /// The proposal status pub status: Status, /// Voting settings (threshold, quorum, etc.) pub voting_strategy: VotingStrategy, @@ -43,6 +46,9 @@ pub struct MultipleChoiceProposal { /// When enabled, proposals can only be executed after the voting /// perid has ended and the proposal passed. pub allow_revoting: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } pub enum VoteResult { @@ -65,14 +71,29 @@ impl MultipleChoiceProposal { /// Gets the current status of the proposal. pub fn current_status(&self, block: &BlockInfo) -> StdResult { - if self.status == Status::Open && self.is_passed(block)? { - Ok(Status::Passed) - } else if self.status == Status::Open - && (self.expiration.is_expired(block) || self.is_rejected(block)?) - { - Ok(Status::Rejected) - } else { - Ok(self.status) + match self.status { + Status::Open if self.is_passed(block)? => match &self.veto { + // if prop is passed and veto is configured, calculate timelock + // expiration. if it's expired, this proposal has passed. + // otherwise, set status to `VetoTimelock`. + Some(veto_config) => { + let expiration = veto_config.timelock_duration.after(block); + + if expiration.is_expired(block) { + Ok(Status::Passed) + } else { + Ok(Status::VetoTimelock { + expiration: veto_config.timelock_duration.after(block), + }) + } + } + // Otherwise the proposal is simply passed + None => Ok(Status::Passed), + }, + Status::Open if self.expiration.is_expired(block) || self.is_rejected(block)? => { + Ok(Status::Rejected) + } + _ => Ok(self.status), } } @@ -306,6 +327,7 @@ mod tests { votes, allow_revoting, min_voting_period: None, + veto: None, } } diff --git a/contracts/proposal/dao-proposal-multiple/src/state.rs b/contracts/proposal/dao-proposal-multiple/src/state.rs index f9c6baa59..2261f6d03 100644 --- a/contracts/proposal/dao-proposal-multiple/src/state.rs +++ b/contracts/proposal/dao-proposal-multiple/src/state.rs @@ -7,6 +7,7 @@ use cw_utils::Duration; use dao_voting::{ multiple_choice::{MultipleChoiceVote, VotingStrategy}, pre_propose::ProposalCreationPolicy, + veto::VetoConfig, }; /// The proposal module's configuration. @@ -43,6 +44,9 @@ pub struct Config { /// remain open until the DAO's treasury was large enough for it to be /// executed. pub close_proposal_on_execution_failure: bool, + /// Optional veto configuration. If set to `None`, veto option + /// is disabled. Otherwise contains the configuration for veto flow. + pub veto: Option, } // Each ballot stores a chosen vote and corresponding voting power and rationale. 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 11f052cd6..3c27c98d1 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs @@ -280,6 +280,7 @@ pub fn test_allow_voting_after_proposal_execution_pre_expiration_cw20() { false, ), close_proposal_on_execution_failure: true, + veto: None, }; let core_addr = instantiate_with_multiple_staked_balances_governance( diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs index c1430c2f9..e8b5744bf 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs @@ -130,6 +130,7 @@ where voting_strategy, close_proposal_on_execution_failure: true, pre_propose_info, + veto: None, }; let governance_addr = setup_governance(&mut app, instantiate, Some(initial_balances)); diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 3a0dc89e2..bd954913b 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -65,6 +65,7 @@ pub fn _get_default_token_dao_proposal_module_instantiate(app: &mut App) -> Inst false, ), close_proposal_on_execution_failure: true, + veto: None, } } @@ -81,6 +82,7 @@ fn _get_default_non_token_dao_proposal_module_instantiate(app: &mut App) -> Inst allow_revoting: false, pre_propose_info: get_pre_propose_info(app, None, false), close_proposal_on_execution_failure: true, + veto: None, } } diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index e1b8dddde..a717296e6 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -122,6 +122,7 @@ fn test_propose() { min_voting_period: None, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -137,6 +138,7 @@ fn test_propose() { voting_strategy: voting_strategy.clone(), min_voting_period: None, close_proposal_on_execution_failure: true, + veto: None, }; assert_eq!(config, expected); @@ -177,6 +179,7 @@ fn test_propose() { }, allow_revoting: false, min_voting_period: None, + veto: None, }; assert_eq!(created.proposal, expected); @@ -201,6 +204,7 @@ fn test_propose_wrong_num_choices() { allow_revoting: false, voting_strategy: voting_strategy.clone(), pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -216,6 +220,7 @@ fn test_propose_wrong_num_choices() { allow_revoting: false, dao: core_addr, voting_strategy, + veto: None, }; assert_eq!(config, expected); @@ -277,6 +282,7 @@ fn test_proposal_count_initialized_to_zero() { only_members_execute: true, allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, msg, None); @@ -311,6 +317,7 @@ fn test_no_early_pass_with_min_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -405,6 +412,7 @@ fn test_propose_with_messages() { only_members_execute: true, allow_revoting: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -441,6 +449,7 @@ fn test_propose_with_messages() { only_members_execute: false, allow_revoting: false, dao: "dao".to_string(), + veto: None, }; let wasm_msg = WasmMsg::Execute { @@ -524,6 +533,7 @@ fn test_min_duration_units_missmatch() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; instantiate_with_staked_balances_governance( &mut app, @@ -556,6 +566,7 @@ fn test_min_duration_larger_than_proposal_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; instantiate_with_staked_balances_governance( &mut app, @@ -587,6 +598,7 @@ fn test_min_duration_same_as_proposal_duration() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -707,6 +719,7 @@ fn test_voting_module_token_proposal_deposit_instantiate() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -782,6 +795,7 @@ fn test_different_token_proposal_deposit() { }), false, ), + veto: None, }; instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -843,6 +857,7 @@ fn test_bad_token_proposal_deposit() { }), false, ), + veto: None, }; instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -873,6 +888,7 @@ fn test_take_proposal_deposit() { }), false, ), + veto: None, }; let core_addr = instantiate_with_cw20_balances_governance( @@ -978,6 +994,7 @@ fn test_native_proposal_deposit() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1403,6 +1420,7 @@ fn test_cant_propose_zero_power() { }), false, ), + veto: None, }; let core_addr = instantiate_with_cw20_balances_governance( @@ -1567,6 +1585,7 @@ fn test_cant_execute_not_member() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1657,6 +1676,7 @@ fn test_cant_execute_not_member_when_proposal_created() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -1774,6 +1794,7 @@ fn test_open_proposal_submission() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: get_pre_propose_info(&mut app, None, true), + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); let govmod = query_multiple_proposal_module(&app, &core_addr); @@ -1842,6 +1863,7 @@ fn test_open_proposal_submission() { votes: MultipleChoiceVotes { vote_weights: vec![Uint128::zero(); 3], }, + veto: None, }; assert_eq!(created.proposal, expected); @@ -2067,6 +2089,7 @@ fn test_execute_expired_proposal() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( @@ -2229,6 +2252,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: dao.to_string(), + veto: None, }, &[], ) @@ -2248,6 +2272,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR).to_string(), + veto: None, }, &[], ) @@ -2265,6 +2290,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR), + veto: None, }; assert_eq!(govmod_config, expected); @@ -2283,6 +2309,7 @@ fn test_update_config() { only_members_execute: false, allow_revoting: false, dao: Addr::unchecked(CREATOR_ADDR).to_string(), + veto: None, }, &[], ) @@ -2358,6 +2385,7 @@ fn test_query_list_proposals() { allow_revoting: false, voting_strategy: voting_strategy.clone(), pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let gov_addr = instantiate_with_staked_balances_governance( &mut app, @@ -2438,6 +2466,7 @@ fn test_query_list_proposals() { }, allow_revoting: false, min_voting_period: None, + veto: None, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2466,6 +2495,7 @@ fn test_query_list_proposals() { }, allow_revoting: false, min_voting_period: None, + veto: None, }, }; assert_eq!(proposals_forward.proposals[0], expected); @@ -2491,6 +2521,7 @@ fn test_hooks() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); @@ -2617,6 +2648,7 @@ fn test_active_threshold_absolute() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staking_active_threshold( @@ -2744,6 +2776,7 @@ fn test_active_threshold_percent() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; // 20% needed to be active, 20% of 100000000 is 20000000 @@ -2872,6 +2905,7 @@ fn test_active_threshold_none() { allow_revoting: false, voting_strategy, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = @@ -2982,6 +3016,7 @@ fn test_revoting() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3114,6 +3149,7 @@ fn test_allow_revoting_config_changes() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3171,6 +3207,7 @@ fn test_allow_revoting_config_changes() { quorum: PercentageThreshold::Majority {}, }, close_proposal_on_execution_failure: false, + veto: None, }, &[], ) @@ -3265,6 +3302,7 @@ fn test_revoting_same_vote_twice() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3359,6 +3397,7 @@ fn test_invalid_revote_does_not_invalidate_initial_vote() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -3550,6 +3589,7 @@ fn test_close_failed_proposal() { allow_revoting: false, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staking_active_threshold(&mut app, instantiate, None, None); @@ -3684,6 +3724,7 @@ fn test_close_failed_proposal() { allow_revoting: false, dao: original.dao.to_string(), close_proposal_on_execution_failure: false, + veto: None, }) .unwrap(), funds: vec![], @@ -3798,6 +3839,7 @@ fn test_no_double_refund_on_execute_fail_and_close() { }), false, ), + veto: None, }; let core_addr = instantiate_with_staking_active_threshold( @@ -3976,6 +4018,7 @@ pub fn test_not_allow_voting_on_expired_proposal() { min_voting_period: None, close_proposal_on_execution_failure: true, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }; let core_addr = instantiate_with_staked_balances_governance( &mut app, @@ -4067,6 +4110,7 @@ fn test_next_proposal_id() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4138,6 +4182,7 @@ fn test_vote_with_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4234,6 +4279,7 @@ fn test_revote_with_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin { @@ -4388,6 +4434,7 @@ fn test_update_rationale() { }, close_proposal_on_execution_failure: false, pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, + veto: None, }, Some(vec![ Cw20Coin {