diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index 39dab44263..e76a4f3e28 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -1,8 +1,9 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{EpochEventId, EpochState, IdentityKey, MixId}; +use crate::{EpochEventId, EpochState, IdentityKey, MixId, ProfitMarginRange}; use contracts_common::signing::verifier::ApiVerifierError; +use contracts_common::Percent; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; use thiserror::Error; @@ -240,6 +241,12 @@ pub enum MixnetContractError { #[error("this delegation has not been performed with the vesting tokens or has already been migrated")] NotAVestingDelegation, + + #[error("the provided profit margin ({provided}) is outside the allowed range: {range}")] + ProfitMarginOutsideRange { + provided: Percent, + range: ProfitMarginRange, + }, } impl MixnetContractError { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index b412997088..ddcbb9326b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -7,6 +7,7 @@ use contracts_common::Percent; use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cosmwasm_std::Coin; +use std::fmt::{Display, Formatter}; use std::ops::Index; // type aliases for better reasoning about available data @@ -23,6 +24,12 @@ pub struct ProfitMarginRange { pub maximum: Percent, } +impl Display for ProfitMarginRange { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} - {}", self.minimum, self.maximum) + } +} + impl Default for ProfitMarginRange { fn default() -> Self { ProfitMarginRange { @@ -42,6 +49,10 @@ impl ProfitMarginRange { profit_margin } } + + pub fn within_range(&self, profit_margin: Percent) -> bool { + profit_margin >= self.minimum && profit_margin <= self.maximum + } } /// Specifies layer assignment for the given mixnode. diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 703769365e..ab1aa9bd86 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -24,7 +24,7 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature; use crate::signing::storage as signing_storage; use crate::support::helpers::{ ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond, - ensure_no_pending_pledge_changes, validate_pledge, + ensure_no_pending_pledge_changes, ensure_profit_margin_within_range, validate_pledge, }; use super::storage; @@ -69,6 +69,9 @@ pub(crate) fn try_add_mixnode( cost_params: MixNodeCostParams, owner_signature: MessageSignature, ) -> Result { + // ensure the profit margin is within the defined range + ensure_profit_margin_within_range(deps.storage, cost_params.profit_margin_percent)?; + // check if the pledge contains any funds of the appropriate denomination let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?; let pledge = validate_pledge(info.funds, minimum_pledge)?; @@ -295,6 +298,9 @@ pub(crate) fn try_update_mixnode_cost_params( ensure_bonded(&existing_bond)?; + // ensure the profit margin is within the defined range + ensure_profit_margin_within_range(deps.storage, new_costs.profit_margin_percent)?; + let cosmos_event = new_mixnode_pending_cost_params_update_event( existing_bond.mix_id, &info.sender, diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 1b03a238be..0d8b679d3f 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -7,6 +7,7 @@ use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::mixnode::PendingMixNodeChanges; use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond}; +use nym_contracts_common::Percent; // helper trait to attach `Msg` to a response if it's provided #[allow(dead_code)] @@ -267,3 +268,18 @@ pub(crate) fn decode_ed25519_identity_key( Ok(public_key) } + +pub(crate) fn ensure_profit_margin_within_range( + storage: &dyn Storage, + profit_margin: Percent, +) -> Result<(), MixnetContractError> { + let range = mixnet_params_storage::profit_margin_range(storage)?; + if !range.within_range(profit_margin) { + return Err(MixnetContractError::ProfitMarginOutsideRange { + provided: profit_margin, + range, + }); + } + + Ok(()) +}