introducing allowed range of operator interval operating cost

This commit is contained in:
Jędrzej Stuczyński
2024-07-18 12:11:47 +01:00
committed by fmtabbara
parent c2ab47a102
commit 9d0fd681d4
12 changed files with 113 additions and 22 deletions
@@ -1,7 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochEventId, EpochState, IdentityKey, MixId, ProfitMarginRange};
use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, ProfitMarginRange};
use contracts_common::signing::verifier::ApiVerifierError;
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
@@ -247,6 +247,13 @@ pub enum MixnetContractError {
provided: Percent,
range: ProfitMarginRange,
},
#[error("the provided interval operating cost ({provided}{denom}) is outside the allowed range: {range}")]
OperatingCostOutsideRange {
denom: String,
provided: Uint128,
range: OperatingCostRange,
},
}
impl MixnetContractError {
@@ -11,7 +11,8 @@ use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{
Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, ProfitMarginRange, SphinxKey,
Delegation, EpochEventId, EpochId, IdentityKey, MixId, OperatingCostRange, Percent,
ProfitMarginRange, SphinxKey,
};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
@@ -159,6 +160,11 @@ impl MixNodeRewarding {
allowed_range.normalise(self.cost_params.profit_margin_percent)
}
pub fn normalise_operating_cost(&mut self, allowed_range: OperatingCostRange) {
self.cost_params.interval_operating_cost.amount =
allowed_range.normalise(self.cost_params.interval_operating_cost.amount)
}
/// Determines whether this node is still bonded. This is performed via a simple check,
/// if there are no tokens left associated with the operator, it means they have unbonded
/// and those params only exist for the purposes of calculating rewards for delegators that
@@ -12,7 +12,7 @@ use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::types::{ContractStateParams, LayerAssignment, MixId};
use crate::ProfitMarginRange;
use crate::{OperatingCostRange, ProfitMarginRange};
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Decimal};
@@ -61,6 +61,9 @@ pub struct InstantiateMsg {
#[serde(default)]
pub profit_margin: ProfitMarginRange,
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
#[cw_serde]
@@ -5,8 +5,8 @@ use crate::error::MixnetContractError;
use crate::Layer;
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use cosmwasm_std::{Addr, Uint128};
use std::fmt::{Display, Formatter};
use std::ops::Index;
@@ -18,13 +18,20 @@ pub type MixId = u32;
pub type BlockHeight = u64;
#[cw_serde]
#[derive(Copy)]
pub struct ProfitMarginRange {
pub minimum: Percent,
pub maximum: Percent,
pub struct RangedValue<T> {
pub minimum: T,
pub maximum: T,
}
impl Display for ProfitMarginRange {
impl<T> Copy for RangedValue<T> where T: Copy {}
pub type ProfitMarginRange = RangedValue<Percent>;
pub type OperatingCostRange = RangedValue<Uint128>;
impl<T> Display for RangedValue<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} - {}", self.minimum, self.maximum)
}
@@ -39,19 +46,33 @@ impl Default for ProfitMarginRange {
}
}
impl ProfitMarginRange {
pub fn normalise(&self, profit_margin: Percent) -> Percent {
if profit_margin < self.minimum {
impl Default for OperatingCostRange {
fn default() -> Self {
OperatingCostRange {
minimum: Uint128::zero(),
// 1 billion (native tokens, i.e. 1 billion * 1'000'000 base tokens) - the total supply
maximum: Uint128::new(1_000_000_000_000_000),
}
}
}
impl<T> RangedValue<T>
where
T: Copy + PartialOrd + PartialEq,
{
pub fn normalise(&self, value: T) -> T {
if value < self.minimum {
self.minimum
} else if profit_margin > self.maximum {
} else if value > self.maximum {
self.maximum
} else {
profit_margin
value
}
}
pub fn within_range(&self, profit_margin: Percent) -> bool {
profit_margin >= self.minimum && profit_margin <= self.maximum
pub fn within_range(&self, value: T) -> bool {
value >= self.minimum && value <= self.maximum
}
}
@@ -199,4 +220,9 @@ pub struct ContractStateParams {
/// default: 0% - 100%
#[serde(default)]
pub profit_margin: ProfitMarginRange,
/// Defines the allowed interval operating cost range of operators.
/// default: 0 - 1'000'000'000'000'000 (1 Billion native tokens - the total supply)
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
+13 -2
View File
@@ -12,7 +12,7 @@ use cosmwasm_std::{
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg,
ProfitMarginRange, QueryMsg,
OperatingCostRange, ProfitMarginRange, QueryMsg,
};
use nym_contracts_common::set_build_information;
@@ -26,6 +26,7 @@ fn default_initial_state(
rewarding_denom: String,
vesting_contract_address: Addr,
profit_margin: ProfitMarginRange,
interval_operating_cost: OperatingCostRange,
) -> ContractState {
ContractState {
owner,
@@ -43,6 +44,7 @@ fn default_initial_state(
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT,
},
profit_margin,
interval_operating_cost,
},
}
}
@@ -75,6 +77,7 @@ pub fn instantiate(
msg.rewarding_denom,
vesting_contract_address,
msg.profit_margin,
msg.interval_operating_cost,
);
let starting_interval =
Interval::init_interval(msg.epochs_in_interval, msg.epoch_duration, &env);
@@ -551,7 +554,7 @@ pub fn migrate(
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::Decimal;
use cosmwasm_std::{Decimal, Uint128};
use mixnet_contract_common::reward_params::{IntervalRewardParams, RewardingParams};
use mixnet_contract_common::{InitialRewardingParams, Percent};
use std::time::Duration;
@@ -581,6 +584,10 @@ mod tests {
minimum: "0.05".parse().unwrap(),
maximum: "0.95".parse().unwrap(),
},
interval_operating_cost: OperatingCostRange {
minimum: "1000".parse().unwrap(),
maximum: "10000".parse().unwrap(),
},
};
let sender = mock_info("sender", &[]);
@@ -606,6 +613,10 @@ mod tests {
minimum: Percent::from_percentage_value(5).unwrap(),
maximum: Percent::from_percentage_value(95).unwrap(),
},
interval_operating_cost: OperatingCostRange {
minimum: Uint128::new(1000),
maximum: Uint128::new(10000),
},
},
};
@@ -46,6 +46,7 @@ pub(crate) mod tests {
minimum_mixnode_pledge: coin(123u128, "unym"),
minimum_gateway_pledge: coin(456u128, "unym"),
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
},
};
@@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Storage};
use cosmwasm_std::{Coin, StdResult};
use cw_storage_plus::Item;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{ContractState, ProfitMarginRange};
use mixnet_contract_common::{ContractState, OperatingCostRange, ProfitMarginRange};
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
@@ -36,6 +36,14 @@ pub(crate) fn profit_margin_range(
.map(|state| state.params.profit_margin)?)
}
pub(crate) fn interval_oprating_cost_range(
storage: &dyn Storage,
) -> Result<OperatingCostRange, MixnetContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.params.interval_operating_cost)?)
}
#[allow(unused)]
pub(crate) fn minimum_delegation_stake(
storage: &dyn Storage,
@@ -122,6 +122,7 @@ pub mod tests {
amount: INITIAL_GATEWAY_PLEDGE_AMOUNT + Uint128::new(1234),
},
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
};
let initial_params = storage::CONTRACT_STATE
@@ -24,7 +24,9 @@ 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, ensure_profit_margin_within_range, validate_pledge,
ensure_no_pending_pledge_changes, ensure_operating_cost_within_range,
ensure_profit_margin_within_range,
validate_pledge,
};
use super::storage;
@@ -72,6 +74,9 @@ pub(crate) fn try_add_mixnode(
// ensure the profit margin is within the defined range
ensure_profit_margin_within_range(deps.storage, cost_params.profit_margin_percent)?;
// ensure the operating cost is within the defined range
ensure_operating_cost_within_range(deps.storage, &cost_params.interval_operating_cost)?;
// 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)?;
@@ -301,6 +306,9 @@ pub(crate) fn try_update_mixnode_cost_params(
// ensure the profit margin is within the defined range
ensure_profit_margin_within_range(deps.storage, new_costs.profit_margin_percent)?;
// ensure the operating cost is within the defined range
ensure_operating_cost_within_range(deps.storage, &new_costs.interval_operating_cost)?;
let cosmos_event = new_mixnode_pending_cost_params_update_event(
existing_bond.mix_id,
&info.sender,
+5 -2
View File
@@ -108,8 +108,11 @@ pub(crate) fn try_reward_mixnode(
// make sure node's profit margin is within the allowed range,
// if not adjust it accordingly
let profit_margin_range = mixnet_params_storage::profit_margin_range(deps.storage)?;
mix_rewarding.normalise_profit_margin(profit_margin_range);
let params = mixnet_params_storage::CONTRACT_STATE
.load(deps.storage)?
.params;
mix_rewarding.normalise_profit_margin(params.profit_margin);
mix_rewarding.normalise_operating_cost(params.interval_operating_cost);
let rewarding_params = storage::REWARDING_PARAMS.load(deps.storage)?;
let node_reward_params = NodeRewardParams::new(node_performance, node_status.is_active());
+16
View File
@@ -283,3 +283,19 @@ pub(crate) fn ensure_profit_margin_within_range(
Ok(())
}
pub fn ensure_operating_cost_within_range(
storage: &dyn Storage,
operating_cost: &Coin,
) -> Result<(), MixnetContractError> {
let range = mixnet_params_storage::interval_oprating_cost_range(storage)?;
if !range.within_range(operating_cost.amount) {
return Err(MixnetContractError::OperatingCostOutsideRange {
denom: operating_cost.denom.clone(),
provided: operating_cost.amount,
range,
});
}
Ok(())
}
@@ -997,6 +997,7 @@ pub mod test_helpers {
epoch_duration: Duration::from_secs(60 * 60),
initial_rewarding_params: initial_rewarding_params(),
profit_margin: Default::default(),
interval_operating_cost: Default::default(),
};
let env = mock_env();
let info = mock_info("creator", &[]);