diff --git a/contracts/tgrade-validator-voting/src/multitest/proposals.rs b/contracts/tgrade-validator-voting/src/multitest/proposals.rs index a0c223ce..0e477ce4 100644 --- a/contracts/tgrade-validator-voting/src/multitest/proposals.rs +++ b/contracts/tgrade-validator-voting/src/multitest/proposals.rs @@ -105,3 +105,28 @@ fn cancel_upgrade() { // We canceled the upgrade, so there should be no upgrade planned on the chain. assert_eq!(suite.check_upgrade().unwrap(), None); } + +#[test] +fn change_params() { + let rules = RulesBuilder::new() + .with_threshold(Decimal::percent(50)) + .build(); + + let mut suite = SuiteBuilder::new() + .with_group_member("member", 1) + .with_voting_rules(rules) + .build(); + + // We haven't executed a params change, so nothing yet + assert_eq!(suite.check_params().unwrap(), None); + + let proposal = suite.propose_change_params("member").unwrap(); + let proposal_id = get_proposal_id(&proposal).unwrap(); + suite.execute("member", proposal_id).unwrap(); + + // There should now be a param change in the map + assert_eq!( + suite.check_params().unwrap(), + Some(vec![("foo/bar".to_string(), "baz".to_string())]) + ); +} diff --git a/contracts/tgrade-validator-voting/src/multitest/suite.rs b/contracts/tgrade-validator-voting/src/multitest/suite.rs index 48fdbdf9..5804e189 100644 --- a/contracts/tgrade-validator-voting/src/multitest/suite.rs +++ b/contracts/tgrade-validator-voting/src/multitest/suite.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{to_binary, Addr, ContractInfoResponse, Decimal}; use cw_multi_test::{AppResponse, Contract, ContractWrapper, Executor}; use tg3::Status; use tg4::{Member, Tg4ExecuteMsg}; -use tg_bindings::{TgradeMsg, TgradeQuery}; +use tg_bindings::{ParamChange, TgradeMsg, TgradeQuery}; use tg_bindings_test::{TgradeApp, UpgradePlan}; use crate::msg::ValidatorProposal; @@ -247,6 +247,19 @@ impl Suite { ) } + pub fn propose_change_params(&mut self, executor: &str) -> AnyResult { + self.propose( + executor, + "proposal title", + "proposal description", + ValidatorProposal::ChangeParams(vec![ParamChange { + subspace: "foo".to_string(), + key: "bar".to_string(), + value: "baz".to_string(), + }]), + ) + } + pub fn check_pinned(&self, code_id: u64) -> AnyResult { Ok(self .app @@ -259,6 +272,16 @@ impl Suite { .read_module(|router, _, storage| router.custom.upgrade_is_planned(storage))?) } + pub fn check_params(&self) -> AnyResult>> { + let params = self + .app + .read_module(|router, _, storage| router.custom.get_params(storage))?; + Ok(match params.len() { + 0 => None, + _ => Some(params), + }) + } + pub fn execute(&mut self, executor: &str, proposal_id: u64) -> AnyResult { self.app.execute_contract( Addr::unchecked(executor), diff --git a/packages/bindings-test/src/multitest.rs b/packages/bindings-test/src/multitest.rs index 36330214..78c8788c 100644 --- a/packages/bindings-test/src/multitest.rs +++ b/packages/bindings-test/src/multitest.rs @@ -11,6 +11,7 @@ use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage}; use cosmwasm_std::OwnedDeps; use std::marker::PhantomData; +use cosmwasm_std::Order::Ascending; use cosmwasm_std::{ from_slice, to_binary, Addr, Api, Binary, BlockInfo, Coin, CustomQuery, Empty, Order, Querier, QuerierResult, StdError, StdResult, Storage, Timestamp, @@ -38,6 +39,7 @@ const PRIVILEGES: Map<&Addr, Privileges> = Map::new("privileges"); const VOTES: Item = Item::new("votes"); const PINNED: Item> = Item::new("pinned"); const PLANNED_UPGRADE: Item = Item::new("planned_upgrade"); +const PARAMS: Map = Map::new("params"); const ADMIN_PRIVILEGES: &[Privilege] = &[ Privilege::GovProposalExecutor, @@ -82,6 +84,10 @@ impl TgradeModule { PLANNED_UPGRADE.may_load(storage) } + pub fn get_params(&self, storage: &dyn Storage) -> StdResult> { + PARAMS.range(storage, None, None, Ascending).collect() + } + fn require_privilege( &self, storage: &dyn Storage, @@ -244,6 +250,26 @@ impl Module for TgradeModule { GovProposal::MigrateContract { .. } => { bail!("GovProposal::MigrateContract not implemented") } + GovProposal::ChangeParams(params) => { + let mut sorted_params = params.clone(); + sorted_params.sort_unstable(); + sorted_params.dedup_by(|a, b| a.subspace == b.subspace && a.key == b.key); + if sorted_params.len() < params.len() { + return Err(anyhow::anyhow!( + "duplicate subspace + keys in params vector" + )); + } + for p in params { + if p.subspace.is_empty() { + return Err(anyhow::anyhow!("empty subspace key")); + } + if p.key.is_empty() { + return Err(anyhow::anyhow!("empty key key")); + } + PARAMS.save(storage, format!("{}/{}", p.subspace, p.key), &p.value)?; + } + Ok(AppResponse::default()) + } // most are ignored _ => Ok(AppResponse::default()), } diff --git a/packages/bindings/src/gov.rs b/packages/bindings/src/gov.rs index d7931d3e..abc91914 100644 --- a/packages/bindings/src/gov.rs +++ b/packages/bindings/src/gov.rs @@ -89,7 +89,7 @@ pub enum GovProposal { } /// ParamChange defines an individual parameter change, for use in ParameterChangeProposal. -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, JsonSchema, Debug)] pub struct ParamChange { pub subspace: String, pub key: String,