diff --git a/Cargo.lock b/Cargo.lock index ad525671a6a..fafe3bdb0b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5272,6 +5272,7 @@ dependencies = [ "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw2", "humantime-serde", "log", diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs index 95d692d5929..47b67d8f161 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs @@ -42,6 +42,10 @@ pub trait MixnetQueryClient { // state/sys-params-related + async fn admin(&self) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::Admin {}).await + } + async fn get_mixnet_contract_version(&self) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {}) .await @@ -580,6 +584,7 @@ mod tests { msg: MixnetQueryMsg, ) -> u32 { match msg { + MixnetQueryMsg::Admin {} => client.admin().ignore(), MixnetQueryMsg::GetAllFamiliesPaged { limit, start_after } => client .get_all_family_members_paged(start_after, limit) .ignore(), diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs index 26a766a1908..f84becb55fb 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_signing_client.rs @@ -31,6 +31,15 @@ pub trait MixnetSigningClient { // state/sys-params-related + async fn update_admin( + &self, + admin: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract(fee, MixnetExecuteMsg::UpdateAdmin { admin }, vec![]) + .await + } + async fn update_rewarding_validator_address( &self, address: AccountId, @@ -760,6 +769,7 @@ mod tests { msg: MixnetExecuteMsg, ) { match msg { + MixnetExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(), MixnetExecuteMsg::AssignNodeLayer { mix_id, layer } => { client.assign_node_layer(mix_id, layer, None).ignore() } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 31e422a49c1..8968f1c8d66 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } bs58 = "0.4.0" cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +cw-controllers = { workspace = true } cw2 = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_repr = { workspace = true } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 710ca20b160..12f2030acc4 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -5,6 +5,7 @@ use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, Pr use contracts_common::signing::verifier::ApiVerifierError; use contracts_common::Percent; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use cw_controllers::AdminError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -12,6 +13,9 @@ pub enum MixnetContractError { #[error("could not perform contract migration: {comment}")] FailedMigration { comment: String }, + #[error(transparent)] + Admin(#[from] AdminError), + #[error("{source}")] StdErr { #[from] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 90110e0dc6b..863fdc03279 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -110,6 +110,11 @@ impl InitialRewardingParams { #[cw_serde] pub enum ExecuteMsg { + /// Change the admin + UpdateAdmin { + admin: String, + }, + AssignNodeLayer { mix_id: MixId, layer: Layer, @@ -292,6 +297,7 @@ pub enum ExecuteMsg { impl ExecuteMsg { pub fn default_memo(&self) -> String { match self { + ExecuteMsg::UpdateAdmin { admin } => format!("updating contract admin to {admin}"), ExecuteMsg::AssignNodeLayer { mix_id, layer } => { format!("assigning mix {mix_id} for layer {layer:?}") } @@ -408,6 +414,9 @@ impl ExecuteMsg { #[cw_serde] #[cfg_attr(feature = "schema", derive(QueryResponses))] pub enum QueryMsg { + #[cfg_attr(feature = "schema", returns(cw_controllers::AdminResponse))] + Admin {}, + // families /// Gets the list of families registered in this contract. #[cfg_attr(feature = "schema", returns(PagedFamiliesResponse))] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index 18c7fc66069..f95c15eb34d 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -187,7 +187,11 @@ impl Index for LayerDistribution { #[cw_serde] pub struct ContractState { /// Address of the contract owner. - pub owner: Addr, + #[deprecated( + note = "use explicit ADMIN instead. this field will be removed in future release" + )] + #[serde(default)] + pub owner: Option, /// Address of "rewarding validator" (nym-api) that's allowed to send any rewarding-related transactions. pub rewarding_validator_address: Addr, diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 3bdbdb90224..f2e9d29ad23 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1276,6 +1276,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "cw-controllers", "cw-storage-plus", "cw2", "nym-contracts-common", @@ -1295,6 +1296,7 @@ dependencies = [ "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "cw2", "humantime-serde", "log", diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index 94133686d58..957f981f2e0 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -34,6 +34,7 @@ cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cosmwasm-storage = { workspace = true } cosmwasm-derive = { workspace = true } +cw-controllers = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index dc3e521bd2c..c754e65fdb5 100644 --- a/contracts/mixnet/schema/nym-mixnet-contract.json +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -177,6 +177,28 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -1692,6 +1714,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets the list of families registered in this contract.", "type": "object", @@ -2937,6 +2972,21 @@ }, "sudo": null, "responses": { + "admin": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "get_all_delegations": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "PagedAllDelegationsResponse", @@ -8140,7 +8190,6 @@ "description": "The current state of the mixnet contract.", "type": "object", "required": [ - "owner", "params", "rewarding_denom", "rewarding_validator_address", @@ -8149,9 +8198,14 @@ "properties": { "owner": { "description": "Address of the contract owner.", - "allOf": [ + "default": null, + "deprecated": true, + "anyOf": [ { "$ref": "#/definitions/Addr" + }, + { + "type": "null" } ] }, diff --git a/contracts/mixnet/schema/raw/execute.json b/contracts/mixnet/schema/raw/execute.json index 688b57db0ba..c5c773e76d3 100644 --- a/contracts/mixnet/schema/raw/execute.json +++ b/contracts/mixnet/schema/raw/execute.json @@ -2,6 +2,28 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", "oneOf": [ + { + "description": "Change the admin", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ diff --git a/contracts/mixnet/schema/raw/query.json b/contracts/mixnet/schema/raw/query.json index f4738a91fe6..c938a56f643 100644 --- a/contracts/mixnet/schema/raw/query.json +++ b/contracts/mixnet/schema/raw/query.json @@ -2,6 +2,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets the list of families registered in this contract.", "type": "object", diff --git a/contracts/mixnet/schema/raw/response_to_admin.json b/contracts/mixnet/schema/raw/response_to_admin.json new file mode 100644 index 00000000000..c73969ab04b --- /dev/null +++ b/contracts/mixnet/schema/raw/response_to_admin.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "description": "Returned from Admin.query_admin()", + "type": "object", + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/contracts/mixnet/schema/raw/response_to_get_state.json b/contracts/mixnet/schema/raw/response_to_get_state.json index d8c9dc5383e..9103e87b72e 100644 --- a/contracts/mixnet/schema/raw/response_to_get_state.json +++ b/contracts/mixnet/schema/raw/response_to_get_state.json @@ -4,7 +4,6 @@ "description": "The current state of the mixnet contract.", "type": "object", "required": [ - "owner", "params", "rewarding_denom", "rewarding_validator_address", @@ -13,9 +12,14 @@ "properties": { "owner": { "description": "Address of the contract owner.", - "allOf": [ + "default": null, + "deprecated": true, + "anyOf": [ { "$ref": "#/definitions/Addr" + }, + { + "type": "null" } ] }, diff --git a/contracts/mixnet/src/constants.rs b/contracts/mixnet/src/constants.rs index 17a62a25a88..0ea867c00cd 100644 --- a/contracts/mixnet/src/constants.rs +++ b/contracts/mixnet/src/constants.rs @@ -57,6 +57,7 @@ pub const PENDING_INTERVAL_EVENTS_NAMESPACE: &str = "pie"; pub const LAST_EPOCH_EVENT_ID_KEY: &str = "lee"; pub const LAST_INTERVAL_EVENT_ID_KEY: &str = "lie"; +pub const ADMIN_STORAGE_KEY: &str = "admin"; pub const CONTRACT_STATE_KEY: &str = "state"; pub const LAYER_DISTRIBUTION_KEY: &str = "layers"; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 884565d142c..f56af17cd7f 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -28,8 +28,10 @@ fn default_initial_state( profit_margin: ProfitMarginRange, interval_operating_cost: OperatingCostRange, ) -> ContractState { + // we have to temporarily preserve this functionalities until it can be removed + #[allow(deprecated)] ContractState { - owner, + owner: Some(owner), rewarding_validator_address, vesting_contract_address, rewarding_denom: rewarding_denom.clone(), @@ -56,7 +58,7 @@ fn default_initial_state( /// `msg` is the contract initialization message, sort of like a constructor call. #[entry_point] pub fn instantiate( - deps: DepsMut<'_>, + mut deps: DepsMut<'_>, env: Env, info: MessageInfo, msg: InstantiateMsg, @@ -72,7 +74,7 @@ pub fn instantiate( let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?; let vesting_contract_address = deps.api.addr_validate(&msg.vesting_contract_address)?; let state = default_initial_state( - info.sender, + info.sender.clone(), rewarding_validator_address.clone(), msg.rewarding_denom, vesting_contract_address, @@ -90,7 +92,7 @@ pub fn instantiate( starting_interval, rewarding_validator_address, )?; - mixnet_params_storage::initialise_storage(deps.storage, state)?; + mixnet_params_storage::initialise_storage(deps.branch(), state, info.sender)?; mixnode_storage::initialise_storage(deps.storage)?; rewards_storage::initialise_storage(deps.storage, reward_params)?; cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -108,6 +110,11 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::UpdateAdmin { admin } => { + crate::mixnet_contract_settings::transactions::try_update_contract_admin( + deps, info, admin, + ) + } ExecuteMsg::AssignNodeLayer { mix_id, layer } => { crate::mixnodes::transactions::assign_mixnode_layer(deps, info, mix_id, layer) } @@ -335,6 +342,9 @@ pub fn query( QueryMsg::GetState {} => { to_binary(&crate::mixnet_contract_settings::queries::query_contract_state(deps)?) } + QueryMsg::Admin {} => to_binary(&crate::mixnet_contract_settings::queries::query_admin( + deps, + )?), QueryMsg::GetRewardingParams {} => { to_binary(&crate::rewards::queries::query_rewarding_params(deps)?) } @@ -536,6 +546,7 @@ pub fn migrate( cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; crate::queued_migrations::vesting_purge(deps.branch())?; + crate::queued_migrations::explicit_contract_admin(deps.branch())?; // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh @@ -594,8 +605,9 @@ mod tests { let res = instantiate(deps.as_mut(), env, sender, init_msg); assert!(res.is_ok()); + #[allow(deprecated)] let expected_state = ContractState { - owner: Addr::unchecked("sender"), + owner: Some(Addr::unchecked("sender")), rewarding_validator_address: Addr::unchecked("foomp123"), vesting_contract_address: Addr::unchecked("bar456"), rewarding_denom: "uatom".into(), diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index 349545984f8..adc8faa9cc9 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -5,11 +5,12 @@ use super::storage; use crate::interval::helpers::change_interval_config; use crate::interval::pending_events::ContractExecutableEvent; use crate::interval::storage::push_new_interval_event; +use crate::mixnet_contract_settings::storage::ADMIN; use crate::mixnodes::transactions::update_mixnode_layer; use crate::rewards; use crate::rewards::storage as rewards_storage; use crate::support::helpers::{ - ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_is_owner, + ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_authorized, }; use cosmwasm_std::{DepsMut, Env, MessageInfo, Order, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; @@ -325,7 +326,7 @@ pub(crate) fn try_update_interval_config( epoch_duration_secs: u64, force_immediately: bool, ) -> Result { - ensure_is_owner(info.sender, deps.storage)?; + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; if epochs_in_interval == 0 { return Err(MixnetContractError::EpochsInIntervalZero); @@ -1766,6 +1767,7 @@ mod tests { use super::*; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Decimal; + use cw_controllers::AdminError::NotAdmin; use std::time::Duration; #[test] @@ -1831,11 +1833,11 @@ mod tests { 1000, false, ); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_interval_config(test.deps_mut(), env.clone(), random, 100, 1000, false); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_interval_config(test.deps_mut(), env, owner, 100, 1000, false); assert!(res.is_ok()) diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index ca939d43342..5d80fec78ab 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -2,10 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; +use crate::mixnet_contract_settings::storage::ADMIN; use cosmwasm_std::{Deps, StdResult}; +use cw_controllers::AdminResponse; use mixnet_contract_common::{ContractBuildInformation, ContractState, ContractStateParams}; use nym_contracts_common::get_build_information; +pub(crate) fn query_admin(deps: Deps<'_>) -> StdResult { + ADMIN.query_admin(deps) +} + pub(crate) fn query_contract_state(deps: Deps<'_>) -> StdResult { storage::CONTRACT_STATE.load(deps.storage) } @@ -36,8 +42,9 @@ pub(crate) mod tests { fn query_for_contract_settings_works() { let mut deps = test_helpers::init_contract(); + #[allow(deprecated)] let dummy_state = ContractState { - owner: Addr::unchecked("someowner"), + owner: Some(Addr::unchecked("foomp")), rewarding_validator_address: Addr::unchecked("monitor"), vesting_contract_address: Addr::unchecked("foomp"), rewarding_denom: "unym".to_string(), diff --git a/contracts/mixnet/src/mixnet_contract_settings/storage.rs b/contracts/mixnet/src/mixnet_contract_settings/storage.rs index 278b6baacfd..0c8502089e6 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/storage.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/storage.rs @@ -1,14 +1,16 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::constants::CONTRACT_STATE_KEY; -use cosmwasm_std::{Addr, Storage}; +use crate::constants::{ADMIN_STORAGE_KEY, CONTRACT_STATE_KEY}; +use cosmwasm_std::{Addr, DepsMut, Storage}; use cosmwasm_std::{Coin, StdResult}; +use cw_controllers::Admin; use cw_storage_plus::Item; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::{ContractState, OperatingCostRange, ProfitMarginRange}; pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY); +pub(crate) const ADMIN: Admin = Admin::new(ADMIN_STORAGE_KEY); pub fn rewarding_validator_address(storage: &dyn Storage) -> Result { Ok(CONTRACT_STATE @@ -66,8 +68,10 @@ pub(crate) fn vesting_contract_address(storage: &dyn Storage) -> Result, initial_state: ContractState, + initial_admin: Addr, ) -> StdResult<()> { - CONTRACT_STATE.save(storage, &initial_state) + CONTRACT_STATE.save(deps.storage, &initial_state)?; + ADMIN.set(deps, Some(initial_admin)) } diff --git a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index 19b753189ec..b458f6fbbc5 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -2,15 +2,36 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; -use cosmwasm_std::DepsMut; +use crate::mixnet_contract_settings::storage::ADMIN; use cosmwasm_std::MessageInfo; use cosmwasm_std::Response; +use cosmwasm_std::{DepsMut, StdResult}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_rewarding_validator_address_update_event, new_settings_update_event, }; use mixnet_contract_common::ContractStateParams; +pub fn try_update_contract_admin( + mut deps: DepsMut<'_>, + info: MessageInfo, + new_admin: String, +) -> Result { + let new_admin = deps.api.addr_validate(&new_admin)?; + + let res = ADMIN.execute_update_admin(deps.branch(), info, Some(new_admin.clone()))?; + + // SAFETY: we don't need to perform any authentication checks on the sender as it was performed + // during 'execute_update_admin' call + #[allow(deprecated)] + storage::CONTRACT_STATE.update(deps.storage, |mut state| -> StdResult<_> { + state.owner = Some(new_admin); + Ok(state) + })?; + + Ok(res) +} + pub fn try_update_rewarding_validator_address( deps: DepsMut<'_>, info: MessageInfo, @@ -18,9 +39,7 @@ pub fn try_update_rewarding_validator_address( ) -> Result { let mut state = storage::CONTRACT_STATE.load(deps.storage)?; - if info.sender != state.owner { - return Err(MixnetContractError::Unauthorized); - } + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; let new_address = deps.api.addr_validate(&address)?; let old_address = state.rewarding_validator_address; @@ -43,10 +62,7 @@ pub(crate) fn try_update_contract_settings( ) -> Result { let mut state = storage::CONTRACT_STATE.load(deps.storage)?; - // check if this is executed by the owner, if not reject the transaction - if info.sender != state.owner { - return Err(MixnetContractError::Unauthorized); - } + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; let response = Response::new().add_event(new_settings_update_event(&state.params, ¶ms)); @@ -65,6 +81,7 @@ pub mod tests { use crate::support::tests::test_helpers; use cosmwasm_std::testing::mock_info; use cosmwasm_std::{Addr, Coin, Uint128}; + use cw_controllers::AdminError::NotAdmin; #[test] fn update_contract_rewarding_validtor_address() { @@ -76,7 +93,7 @@ pub mod tests { info, "not-the-creator".to_string(), ); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let info = mock_info("creator", &[]); let res = try_update_rewarding_validator_address( @@ -136,7 +153,7 @@ pub mod tests { // cannot be updated from non-owner account let info = mock_info("not-the-creator", &[]); let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone()); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); // but works fine from the creator account let info = mock_info("creator", &[]); diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index ab410897e92..248a33d30e7 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::interval::storage as interval_storage; +use crate::mixnet_contract_settings::storage as mixnet_params_storage; use cosmwasm_std::{DepsMut, Order, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::PendingEpochEventKind; @@ -49,3 +50,17 @@ pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> { Ok(()) } + +pub(crate) fn explicit_contract_admin(deps: DepsMut) -> Result<(), MixnetContractError> { + // we need to read the deprecated field to migrate it over + #[allow(deprecated)] + // SAFETY: this value should ALWAYS exist on the first execution of this migration; + // as a matter of fact, it should ALWAYS continue existing until another migration + #[allow(clippy::expect_used)] + let existing_admin = mixnet_params_storage::CONTRACT_STATE + .load(deps.storage)? + .owner + .expect("the contract state is corrupt - there's no admin set"); + mixnet_params_storage::ADMIN.set(deps, Some(existing_admin))?; + Ok(()) +} diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 3f9b0e329a1..f772dca800c 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -6,13 +6,13 @@ use crate::delegations::storage as delegations_storage; use crate::interval::storage as interval_storage; use crate::interval::storage::{push_new_epoch_event, push_new_interval_event}; use crate::mixnet_contract_settings::storage as mixnet_params_storage; +use crate::mixnet_contract_settings::storage::ADMIN; use crate::mixnodes::helpers::get_mixnode_details_by_owner; use crate::mixnodes::storage as mixnodes_storage; use crate::rewards::helpers; use crate::rewards::helpers::update_and_save_last_rewarded; use crate::support::helpers::{ - ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner, - AttachSendTokens, + ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, AttachSendTokens, }; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use mixnet_contract_common::error::MixnetContractError; @@ -227,7 +227,7 @@ pub(crate) fn try_update_active_set_size( active_set_size: u32, force_immediately: bool, ) -> Result { - ensure_is_owner(info.sender, deps.storage)?; + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; let mut rewarding_params = storage::REWARDING_PARAMS.load(deps.storage)?; if active_set_size == 0 { @@ -273,7 +273,7 @@ pub(crate) fn try_update_rewarding_params( updated_params: IntervalRewardingParamsUpdate, force_immediately: bool, ) -> Result { - ensure_is_owner(info.sender, deps.storage)?; + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; if !updated_params.contains_updates() { return Err(MixnetContractError::EmptyParamsChangeMsg); @@ -1796,6 +1796,7 @@ pub mod tests { #[cfg(test)] mod updating_active_set { + use cw_controllers::AdminError::NotAdmin; use mixnet_contract_common::EpochStatus; use crate::support::tests::test_helpers::TestSetup; @@ -1858,10 +1859,10 @@ pub mod tests { 42, false, ); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_active_set_size(test.deps_mut(), env.clone(), random, 42, false); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_active_set_size(test.deps_mut(), env, owner, 42, false); assert!(res.is_ok()) @@ -2002,6 +2003,7 @@ pub mod tests { #[cfg(test)] mod updating_rewarding_params { use cosmwasm_std::Decimal; + use cw_controllers::AdminError::NotAdmin; use mixnet_contract_common::EpochStatus; @@ -2085,11 +2087,11 @@ pub mod tests { update, false, ); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_rewarding_params(test.deps_mut(), env.clone(), random, update, false); - assert_eq!(res, Err(MixnetContractError::Unauthorized)); + assert_eq!(res, Err(MixnetContractError::Admin(NotAdmin {}))); let res = try_update_rewarding_params(test.deps_mut(), env, owner, update, false); assert!(res.is_ok()) diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 09738cea81e..19ddf2bb6e0 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -194,20 +194,6 @@ pub(crate) fn ensure_can_advance_epoch( Ok(epoch_status) } -pub(crate) fn ensure_is_owner( - sender: Addr, - storage: &dyn Storage, -) -> Result<(), MixnetContractError> { - if sender - != crate::mixnet_contract_settings::storage::CONTRACT_STATE - .load(storage)? - .owner - { - return Err(MixnetContractError::Unauthorized); - } - Ok(()) -} - pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> { if bond.is_unbonding { return Err(MixnetContractError::MixnodeIsUnbonding { diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 976138fdc27..dee121b2f07 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -105,10 +105,11 @@ pub mod test_helpers { let deps = init_contract(); let rewarding_validator_address = rewarding_validator_address(deps.as_ref().storage).unwrap(); - let owner = mixnet_params_storage::CONTRACT_STATE - .load(deps.as_ref().storage) + let owner = mixnet_params_storage::ADMIN + .query_admin(deps.as_ref()) .unwrap() - .owner; + .admin + .unwrap(); TestSetup { deps, diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 074ff08f973..382b526b9f2 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3281,6 +3281,7 @@ dependencies = [ "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", + "cw-controllers", "humantime-serde", "log", "nym-contracts-common",