introducing allowed range of operator interval operating cost
This commit is contained in:
committed by
fmtabbara
parent
c2ab47a102
commit
9d0fd681d4
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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", &[]);
|
||||
|
||||
Reference in New Issue
Block a user