From c17f0ac3f87eeb502c17327277bb363bfe996605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 28 Aug 2024 16:30:30 +0100 Subject: [PATCH 1/5] added explicit updateable admin to the mixnet contract --- Cargo.lock | 1 + .../contract_traits/mixnet_query_client.rs | 5 +++ .../contract_traits/mixnet_signing_client.rs | 11 ++++++ .../mixnet-contract/Cargo.toml | 1 + .../mixnet-contract/src/error.rs | 4 +++ .../mixnet-contract/src/msg.rs | 9 +++++ .../mixnet-contract/src/types.rs | 3 -- contracts/Cargo.lock | 2 ++ contracts/mixnet/Cargo.toml | 1 + contracts/mixnet/src/constants.rs | 1 + contracts/mixnet/src/contract.rs | 17 ++++++---- contracts/mixnet/src/interval/transactions.rs | 10 +++--- .../src/mixnet_contract_settings/queries.rs | 7 +++- .../src/mixnet_contract_settings/storage.rs | 12 ++++--- .../mixnet_contract_settings/transactions.rs | 25 +++++++++----- contracts/mixnet/src/queued_migrations.rs | 34 +++++++++++++++++-- contracts/mixnet/src/rewards/transactions.rs | 18 +++++----- contracts/mixnet/src/support/helpers.rs | 14 -------- contracts/mixnet/src/support/tests/mod.rs | 7 ++-- nym-wallet/Cargo.lock | 1 + 20 files changed, 129 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad525671a6..fafe3bdb0b 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 95d692d592..47b67d8f16 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 26a766a190..00b0755477 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, @@ -752,6 +761,7 @@ where mod tests { use super::*; use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue}; + use nym_mixnet_contract_common::ExecuteMsg; // it's enough that this compiles and clippy is happy about it #[allow(dead_code)] @@ -760,6 +770,7 @@ mod tests { msg: MixnetExecuteMsg, ) { match msg { + ExecuteMsg::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 31e422a49c..8968f1c8d6 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 710ca20b16..12f2030acc 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 90110e0dc6..863fdc0327 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 18c7fc6606..e513a392c1 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -186,9 +186,6 @@ impl Index for LayerDistribution { /// The current state of the mixnet contract. #[cw_serde] pub struct ContractState { - /// Address of the contract owner. - pub owner: Addr, - /// 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 3bdbdb9022..f2e9d29ad2 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 94133686d5..957f981f2e 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/src/constants.rs b/contracts/mixnet/src/constants.rs index 17a62a25a8..0ea867c00c 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 884565d142..b9fe131e6d 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -21,7 +21,6 @@ const CONTRACT_NAME: &str = "crate:nym-mixnet-contract"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); fn default_initial_state( - owner: Addr, rewarding_validator_address: Addr, rewarding_denom: String, vesting_contract_address: Addr, @@ -29,7 +28,6 @@ fn default_initial_state( interval_operating_cost: OperatingCostRange, ) -> ContractState { ContractState { - owner, rewarding_validator_address, vesting_contract_address, rewarding_denom: rewarding_denom.clone(), @@ -56,7 +54,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 +70,6 @@ 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, rewarding_validator_address.clone(), msg.rewarding_denom, vesting_contract_address, @@ -90,7 +87,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 +105,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 +337,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 +541,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 @@ -595,7 +601,6 @@ mod tests { assert!(res.is_ok()); let expected_state = ContractState { - owner: 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 349545984f..adc8faa9cc 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 ca939d4334..cfcda13e48 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) } @@ -37,7 +43,6 @@ pub(crate) mod tests { let mut deps = test_helpers::init_contract(); let dummy_state = ContractState { - owner: Addr::unchecked("someowner"), 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 278b6baacf..0c8502089e 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 19b753189e..fc6a9ce30e 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::storage; +use crate::mixnet_contract_settings::storage::ADMIN; use cosmwasm_std::DepsMut; use cosmwasm_std::MessageInfo; use cosmwasm_std::Response; @@ -11,6 +12,16 @@ use mixnet_contract_common::events::{ }; use mixnet_contract_common::ContractStateParams; +pub fn try_update_contract_admin( + deps: DepsMut<'_>, + info: MessageInfo, + new_admin: String, +) -> Result { + let new_admin = deps.api.addr_validate(&new_admin)?; + + Ok(ADMIN.execute_update_admin(deps, info, Some(new_admin))?) +} + pub fn try_update_rewarding_validator_address( deps: DepsMut<'_>, info: MessageInfo, @@ -18,9 +29,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 +52,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 +71,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 +83,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 +143,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 ab410897e9..a6979d8c5c 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,10 +1,14 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::constants::CONTRACT_STATE_KEY; use crate::interval::storage as interval_storage; -use cosmwasm_std::{DepsMut, Order, Storage}; +use crate::mixnet_contract_settings::storage as mixnet_params_storage; +use cosmwasm_std::{Addr, DepsMut, Order, Storage}; +use cw_storage_plus::Item; use mixnet_contract_common::error::MixnetContractError; -use mixnet_contract_common::PendingEpochEventKind; +use mixnet_contract_common::{ContractState, ContractStateParams, PendingEpochEventKind}; +use serde::{Deserialize, Serialize}; fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> { let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?; @@ -49,3 +53,29 @@ pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> { Ok(()) } + +pub(crate) fn explicit_contract_admin(deps: DepsMut) -> Result<(), MixnetContractError> { + #[derive(Deserialize, Serialize)] + pub struct OldContractState { + pub owner: Addr, + pub rewarding_validator_address: Addr, + pub vesting_contract_address: Addr, + pub rewarding_denom: String, + pub params: ContractStateParams, + } + + let old_state_storage = Item::::new(CONTRACT_STATE_KEY); + let old_state = old_state_storage.load(deps.storage)?; + + mixnet_params_storage::initialise_storage( + deps, + ContractState { + rewarding_validator_address: old_state.rewarding_validator_address, + vesting_contract_address: old_state.vesting_contract_address, + rewarding_denom: old_state.rewarding_denom, + params: old_state.params, + }, + old_state.owner, + )?; + Ok(()) +} diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 3f9b0e329a..f772dca800 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 09738cea81..19ddf2bb6e 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 976138fdc2..dee121b2f0 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 074ff08f97..382b526b9f 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", From 07c80e5150a2a629707425802d283c67c95663cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 28 Aug 2024 16:31:39 +0100 Subject: [PATCH 2/5] naming consistency --- .../src/nyxd/contract_traits/mixnet_signing_client.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 00b0755477..f84becb55f 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 @@ -761,7 +761,6 @@ where mod tests { use super::*; use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue}; - use nym_mixnet_contract_common::ExecuteMsg; // it's enough that this compiles and clippy is happy about it #[allow(dead_code)] @@ -770,7 +769,7 @@ mod tests { msg: MixnetExecuteMsg, ) { match msg { - ExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(), + MixnetExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(), MixnetExecuteMsg::AssignNodeLayer { mix_id, layer } => { client.assign_node_layer(mix_id, layer, None).ignore() } From 858b6c6094b76e576da13924a5aa3b2ffdea380f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 3 Sep 2024 14:26:57 +0100 Subject: [PATCH 3/5] restored (and deprecated) 'owner' field in ContractState --- .../mixnet-contract/src/types.rs | 6 ++++ contracts/mixnet/src/contract.rs | 7 ++++ .../src/mixnet_contract_settings/queries.rs | 2 ++ .../mixnet_contract_settings/transactions.rs | 16 +++++++-- contracts/mixnet/src/queued_migrations.rs | 35 +++++-------------- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index e513a392c1..749de1cc7e 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -186,6 +186,12 @@ impl Index for LayerDistribution { /// The current state of the mixnet contract. #[cw_serde] pub struct ContractState { + /// Address of the contract owner. + #[deprecated( + note = "use explicit ADMIN instead. this field will be removed in future release" + )] + pub owner: Addr, + /// Address of "rewarding validator" (nym-api) that's allowed to send any rewarding-related transactions. pub rewarding_validator_address: Addr, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index b9fe131e6d..5e7cacccd7 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -21,13 +21,17 @@ const CONTRACT_NAME: &str = "crate:nym-mixnet-contract"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); fn default_initial_state( + owner: Addr, rewarding_validator_address: Addr, rewarding_denom: String, vesting_contract_address: Addr, profit_margin: ProfitMarginRange, interval_operating_cost: OperatingCostRange, ) -> ContractState { + // we have to temporarily preserve this functionalities until it can be removed + #[allow(deprecated)] ContractState { + owner, rewarding_validator_address, vesting_contract_address, rewarding_denom: rewarding_denom.clone(), @@ -70,6 +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.clone(), rewarding_validator_address.clone(), msg.rewarding_denom, vesting_contract_address, @@ -600,7 +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"), rewarding_validator_address: Addr::unchecked("foomp123"), vesting_contract_address: Addr::unchecked("bar456"), rewarding_denom: "uatom".into(), diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index cfcda13e48..d8844c3adb 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -42,7 +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("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/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index fc6a9ce30e..00da2ae97a 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -3,9 +3,9 @@ use super::storage; use crate::mixnet_contract_settings::storage::ADMIN; -use cosmwasm_std::DepsMut; 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, @@ -13,13 +13,23 @@ use mixnet_contract_common::events::{ use mixnet_contract_common::ContractStateParams; pub fn try_update_contract_admin( - deps: DepsMut<'_>, + mut deps: DepsMut<'_>, info: MessageInfo, new_admin: String, ) -> Result { let new_admin = deps.api.addr_validate(&new_admin)?; - Ok(ADMIN.execute_update_admin(deps, info, Some(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 = new_admin; + Ok(state) + })?; + + Ok(res) } pub fn try_update_rewarding_validator_address( diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index a6979d8c5c..68ee265f3b 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,14 +1,11 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::constants::CONTRACT_STATE_KEY; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; -use cosmwasm_std::{Addr, DepsMut, Order, Storage}; -use cw_storage_plus::Item; +use cosmwasm_std::{DepsMut, Order, Storage}; use mixnet_contract_common::error::MixnetContractError; -use mixnet_contract_common::{ContractState, ContractStateParams, PendingEpochEventKind}; -use serde::{Deserialize, Serialize}; +use mixnet_contract_common::PendingEpochEventKind; fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> { let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?; @@ -55,27 +52,11 @@ pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> { } pub(crate) fn explicit_contract_admin(deps: DepsMut) -> Result<(), MixnetContractError> { - #[derive(Deserialize, Serialize)] - pub struct OldContractState { - pub owner: Addr, - pub rewarding_validator_address: Addr, - pub vesting_contract_address: Addr, - pub rewarding_denom: String, - pub params: ContractStateParams, - } - - let old_state_storage = Item::::new(CONTRACT_STATE_KEY); - let old_state = old_state_storage.load(deps.storage)?; - - mixnet_params_storage::initialise_storage( - deps, - ContractState { - rewarding_validator_address: old_state.rewarding_validator_address, - vesting_contract_address: old_state.vesting_contract_address, - rewarding_denom: old_state.rewarding_denom, - params: old_state.params, - }, - old_state.owner, - )?; + // we need to read the deprecated field to migrate it over + #[allow(deprecated)] + let existing_admin = mixnet_params_storage::CONTRACT_STATE + .load(deps.storage)? + .owner; + mixnet_params_storage::ADMIN.set(deps, Some(existing_admin))?; Ok(()) } From e76c8e06be1b2d0176e857987e742216a4d3ddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 3 Sep 2024 16:35:42 +0100 Subject: [PATCH 4/5] updated contract schema --- .../mixnet/schema/nym-mixnet-contract.json | 51 +++++++++++++++++++ contracts/mixnet/schema/raw/execute.json | 22 ++++++++ contracts/mixnet/schema/raw/query.json | 13 +++++ .../mixnet/schema/raw/response_to_admin.json | 15 ++++++ .../schema/raw/response_to_get_state.json | 1 + 5 files changed, 102 insertions(+) create mode 100644 contracts/mixnet/schema/raw/response_to_admin.json diff --git a/contracts/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index dc3e521bd2..772b9b6068 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", @@ -8149,6 +8199,7 @@ "properties": { "owner": { "description": "Address of the contract owner.", + "deprecated": true, "allOf": [ { "$ref": "#/definitions/Addr" diff --git a/contracts/mixnet/schema/raw/execute.json b/contracts/mixnet/schema/raw/execute.json index 688b57db0b..c5c773e76d 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 f4738a91fe..c938a56f64 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 0000000000..c73969ab04 --- /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 d8c9dc5383..616f678dbd 100644 --- a/contracts/mixnet/schema/raw/response_to_get_state.json +++ b/contracts/mixnet/schema/raw/response_to_get_state.json @@ -13,6 +13,7 @@ "properties": { "owner": { "description": "Address of the contract owner.", + "deprecated": true, "allOf": [ { "$ref": "#/definitions/Addr" From ed7a84a1cebc97821290e2b9c9c760906b4232fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 4 Sep 2024 09:55:23 +0100 Subject: [PATCH 5/5] made 'owner' field optional to prepare for its future removal --- .../cosmwasm-smart-contracts/mixnet-contract/src/types.rs | 3 ++- contracts/mixnet/schema/nym-mixnet-contract.json | 7 +++++-- contracts/mixnet/schema/raw/response_to_get_state.json | 7 +++++-- contracts/mixnet/src/contract.rs | 4 ++-- contracts/mixnet/src/mixnet_contract_settings/queries.rs | 2 +- .../mixnet/src/mixnet_contract_settings/transactions.rs | 2 +- contracts/mixnet/src/queued_migrations.rs | 6 +++++- 7 files changed, 21 insertions(+), 10 deletions(-) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index 749de1cc7e..f95c15eb34 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -190,7 +190,8 @@ pub struct ContractState { #[deprecated( note = "use explicit ADMIN instead. this field will be removed in future release" )] - pub owner: Addr, + #[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/mixnet/schema/nym-mixnet-contract.json b/contracts/mixnet/schema/nym-mixnet-contract.json index 772b9b6068..c754e65fdb 100644 --- a/contracts/mixnet/schema/nym-mixnet-contract.json +++ b/contracts/mixnet/schema/nym-mixnet-contract.json @@ -8190,7 +8190,6 @@ "description": "The current state of the mixnet contract.", "type": "object", "required": [ - "owner", "params", "rewarding_denom", "rewarding_validator_address", @@ -8199,10 +8198,14 @@ "properties": { "owner": { "description": "Address of the contract owner.", + "default": null, "deprecated": true, - "allOf": [ + "anyOf": [ { "$ref": "#/definitions/Addr" + }, + { + "type": "null" } ] }, diff --git a/contracts/mixnet/schema/raw/response_to_get_state.json b/contracts/mixnet/schema/raw/response_to_get_state.json index 616f678dbd..9103e87b72 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,10 +12,14 @@ "properties": { "owner": { "description": "Address of the contract owner.", + "default": null, "deprecated": true, - "allOf": [ + "anyOf": [ { "$ref": "#/definitions/Addr" + }, + { + "type": "null" } ] }, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 5e7cacccd7..f56af17cd7 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -31,7 +31,7 @@ fn default_initial_state( // 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(), @@ -607,7 +607,7 @@ mod tests { #[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/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index d8844c3adb..5d80fec78a 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -44,7 +44,7 @@ pub(crate) mod tests { #[allow(deprecated)] let dummy_state = ContractState { - owner: Addr::unchecked("foomp"), + 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/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index 00da2ae97a..b458f6fbbc 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -25,7 +25,7 @@ pub fn try_update_contract_admin( // during 'execute_update_admin' call #[allow(deprecated)] storage::CONTRACT_STATE.update(deps.storage, |mut state| -> StdResult<_> { - state.owner = new_admin; + state.owner = Some(new_admin); Ok(state) })?; diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 68ee265f3b..248a33d30e 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -54,9 +54,13 @@ pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> { 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; + .owner + .expect("the contract state is corrupt - there's no admin set"); mixnet_params_storage::ADMIN.set(deps, Some(existing_admin))?; Ok(()) }