Finalize implementation
Fix reciprocal Fix, fix reciprocal cargo fmt
This commit is contained in:
@@ -3,5 +3,5 @@ use thiserror::Error;
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum MixnetContractError {
|
||||
#[error("Could not convert ration to f64: {0} / {1}")]
|
||||
InvalidRatio(u128, u128)
|
||||
}
|
||||
InvalidRatio(u128, u128),
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod delegation;
|
||||
pub mod error;
|
||||
mod gateway;
|
||||
mod mixnode;
|
||||
mod msg;
|
||||
mod types;
|
||||
pub mod error;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin};
|
||||
pub use delegation::{
|
||||
|
||||
@@ -15,6 +15,8 @@ use ts_rs::TS;
|
||||
|
||||
use crate::current_block_height;
|
||||
|
||||
const DEFAULT_PROFIT_MARGIN: f64 = 0.1;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
|
||||
pub struct MixNode {
|
||||
pub host: String,
|
||||
@@ -47,6 +49,7 @@ pub struct MixNodeBond {
|
||||
#[serde(default = "current_block_height")]
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub profit_margin: Option<f64>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -56,6 +59,7 @@ impl MixNodeBond {
|
||||
layer: Layer,
|
||||
block_height: u64,
|
||||
mix_node: MixNode,
|
||||
profit_margin: Option<f64>,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
@@ -64,6 +68,15 @@ impl MixNodeBond {
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
profit_margin,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn profit_margin(&self) -> f64 {
|
||||
if let Some(margin) = self.profit_margin {
|
||||
margin
|
||||
} else {
|
||||
DEFAULT_PROFIT_MARGIN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,18 +101,21 @@ impl MixNodeBond {
|
||||
}
|
||||
|
||||
pub fn bond_to_total_stake_ratio(&self, total_stake: Uint128) -> Ratio<u128> {
|
||||
Ratio::new(
|
||||
self.bond_amount().amount.u128(),
|
||||
total_stake.u128(),
|
||||
)
|
||||
Ratio::new(self.bond_amount().amount.u128(), total_stake.u128())
|
||||
}
|
||||
|
||||
pub fn bond_to_total_stake_f64(&self, total_stake: Uint128) -> Result<f64, MixnetContractError> {
|
||||
pub fn bond_to_total_stake_f64(
|
||||
&self,
|
||||
total_stake: Uint128,
|
||||
) -> Result<f64, MixnetContractError> {
|
||||
let ratio = self.bond_to_total_stake_ratio(total_stake);
|
||||
if let Some(f) = ratio.to_f64() {
|
||||
Ok(f)
|
||||
} else {
|
||||
Err(MixnetContractError::InvalidRatio(*ratio.numer(), *ratio.denom()))
|
||||
Err(MixnetContractError::InvalidRatio(
|
||||
*ratio.numer(),
|
||||
*ratio.denom(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,12 +126,18 @@ impl MixNodeBond {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn stake_to_total_stake_f64(&self, total_stake: Uint128) -> Result<f64, MixnetContractError> {
|
||||
pub fn stake_to_total_stake_f64(
|
||||
&self,
|
||||
total_stake: Uint128,
|
||||
) -> Result<f64, MixnetContractError> {
|
||||
let ratio = self.stake_to_total_stake_ratio(total_stake);
|
||||
if let Some(f) = ratio.to_f64() {
|
||||
Ok(f)
|
||||
} else {
|
||||
Err(MixnetContractError::InvalidRatio(*ratio.numer(), *ratio.denom()))
|
||||
Err(MixnetContractError::InvalidRatio(
|
||||
*ratio.numer(),
|
||||
*ratio.denom(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,6 +271,7 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -258,6 +281,7 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -267,6 +291,7 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -276,6 +301,7 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -285,6 +311,7 @@ mod tests {
|
||||
layer: Layer::One,
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -49,7 +49,7 @@ pub enum ExecuteMsg {
|
||||
identity: IdentityKey,
|
||||
// percentage value in range 0-100
|
||||
uptime: u32,
|
||||
performance: f64
|
||||
performance: f64,
|
||||
},
|
||||
|
||||
RewardGateway {
|
||||
|
||||
@@ -30,11 +30,10 @@ pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
|
||||
pub const INITIAL_GATEWAY_ACTIVE_SET_SIZE: u32 = 20;
|
||||
|
||||
// This is totally made up, lets set the pool to billion nyms, so million billion micro nyms
|
||||
pub const INITIAL_INFLATION_POOL: u64 = 1_000_000_000_000_000;
|
||||
pub const INITIAL_INFLATION_POOL: u128 = 1_000_000_000_000_000;
|
||||
// Sybil attack resistance parameter
|
||||
pub const ALPHA: f64 = 0.3;
|
||||
// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
|
||||
pub const DEFAULT_PROFIT_MARGIN: f64 = 0.1; // Assume operators want a 10 % profit
|
||||
pub const DEFAULT_COST_PER_EPOCH: u32 = 40_000_000;
|
||||
|
||||
fn default_initial_state(owner: Addr) -> State {
|
||||
@@ -118,9 +117,11 @@ pub fn execute(
|
||||
ExecuteMsg::RewardMixnode { identity, uptime } => {
|
||||
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
|
||||
}
|
||||
ExecuteMsg::RewardMixnodeV2 { identity, uptime, performance } => {
|
||||
transactions::try_reward_mixnode_v2(deps, env, info, identity, uptime, performance)
|
||||
}
|
||||
ExecuteMsg::RewardMixnodeV2 {
|
||||
identity,
|
||||
uptime,
|
||||
performance,
|
||||
} => transactions::try_reward_mixnode_v2(deps, env, info, identity, uptime, performance),
|
||||
ExecuteMsg::RewardGateway { identity, uptime } => {
|
||||
transactions::try_reward_gateway(deps, env, info, identity, uptime)
|
||||
}
|
||||
@@ -226,7 +227,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
|
||||
}
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
todo!("Calculate initial total mix and gateway stake after initial deployment");
|
||||
todo!("Calculate initial total mix and gateway stake after initial deployment. Update mixnode model with profit sharing");
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
Ok(Default::default())
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::{DEFAULT_COST_PER_EPOCH, INITIAL_INFLATION_POOL};
|
||||
use crate::contract::{
|
||||
DEFAULT_COST_PER_EPOCH, INITIAL_INFLATION_POOL, INITIAL_MIXNODE_ACTIVE_SET_SIZE,
|
||||
};
|
||||
use crate::state::State;
|
||||
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
use crate::{error::ContractError, queries};
|
||||
@@ -14,6 +16,8 @@ use mixnet_contract::{
|
||||
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
|
||||
RawDelegationData, StateParams,
|
||||
};
|
||||
use num::rational::Ratio;
|
||||
use num::ToPrimitive;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -61,11 +65,11 @@ pub fn mut_total_mix_stake(storage: &mut dyn Storage) -> Singleton<Uint128> {
|
||||
singleton(storage, TOTAL_MIX_STAKE_KEY)
|
||||
}
|
||||
|
||||
fn inflation_pool(storage: &dyn Storage) -> ReadonlySingleton<u64> {
|
||||
fn inflation_pool(storage: &dyn Storage) -> ReadonlySingleton<u128> {
|
||||
singleton_read(storage, INFLATION_POOL_PREFIX)
|
||||
}
|
||||
|
||||
pub fn mut_inflation_pool(storage: &mut dyn Storage) -> Singleton<u64> {
|
||||
pub fn mut_inflation_pool(storage: &mut dyn Storage) -> Singleton<u128> {
|
||||
singleton(storage, INFLATION_POOL_PREFIX)
|
||||
}
|
||||
|
||||
@@ -91,7 +95,7 @@ pub fn total_gateway_stake_value(storage: &dyn Storage) -> Uint128 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inflation_pool_value(storage: &dyn Storage) -> u64 {
|
||||
pub fn inflation_pool_value(storage: &dyn Storage) -> u128 {
|
||||
match inflation_pool(storage).load() {
|
||||
Ok(value) => value,
|
||||
Err(_e) => INITIAL_INFLATION_POOL,
|
||||
@@ -134,13 +138,14 @@ pub fn decr_total_gateway_stake(
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub fn incr_inflation_pool(amount: u64, storage: &mut dyn Storage) -> Result<u64, ContractError> {
|
||||
#[allow(dead_code)]
|
||||
pub fn incr_inflation_pool(amount: u128, storage: &mut dyn Storage) -> Result<u128, ContractError> {
|
||||
let stake = inflation_pool_value(storage) + amount;
|
||||
mut_inflation_pool(storage).save(&stake)?;
|
||||
Ok(stake)
|
||||
}
|
||||
|
||||
pub fn decr_inflation_pool(amount: u64, storage: &mut dyn Storage) -> Result<u64, ContractError> {
|
||||
pub fn decr_inflation_pool(amount: u128, storage: &mut dyn Storage) -> Result<u128, ContractError> {
|
||||
// TODO: This could got to < 0
|
||||
let stake = inflation_pool_value(storage) - amount;
|
||||
mut_inflation_pool(storage).save(&stake)?;
|
||||
@@ -319,20 +324,20 @@ pub(crate) fn price_for_uptime(uptime: u32) -> f64 {
|
||||
|
||||
pub(crate) fn increase_mix_delegated_stakes_v2(
|
||||
storage: &mut dyn Storage,
|
||||
mix_identity: IdentityKeyRef,
|
||||
sigma: f64,
|
||||
bond: &MixNodeBond,
|
||||
node_reward: f64,
|
||||
uptime: u32,
|
||||
profit_margin: f64,
|
||||
reward_blockstamp: u64,
|
||||
) -> StdResult<Uint128> {
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
|
||||
let total_mix_stake = total_gateway_stake_value(storage);
|
||||
let one_over_k = 1. / INITIAL_MIXNODE_ACTIVE_SET_SIZE as f64;
|
||||
|
||||
let mut total_rewarded = Uint128::zero();
|
||||
let mut chunk_start: Option<Vec<_>> = None;
|
||||
loop {
|
||||
// get `chunk_size` of delegations
|
||||
let delegations_chunk = mix_delegations_read(storage, mix_identity)
|
||||
let delegations_chunk = mix_delegations_read(storage, bond.identity())
|
||||
.range(chunk_start.as_deref(), None, Order::Ascending)
|
||||
.take(chunk_size)
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
@@ -352,20 +357,40 @@ pub(crate) fn increase_mix_delegated_stakes_v2(
|
||||
.chain(std::iter::once(0u8))
|
||||
.collect(),
|
||||
);
|
||||
let sigma_ratio = if bond.stake_to_total_stake_f64(total_mix_stake)? < one_over_k {
|
||||
bond.stake_to_total_stake_ratio(total_mix_stake)
|
||||
} else {
|
||||
Ratio::new(1, INITIAL_MIXNODE_ACTIVE_SET_SIZE as u128)
|
||||
};
|
||||
|
||||
// and for each of them increase the stake proportionally to the reward
|
||||
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
|
||||
// since they delegated
|
||||
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
|
||||
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
|
||||
let reward = ((1. - profit_margin)
|
||||
// Getting amount / over sigma by reciprocal fraction multiplication'
|
||||
// Delegation rewards almost certanly need to be scaled by total_stake
|
||||
let delegation_over_sigma_ratio = Ratio::new(
|
||||
delegation.amount.u128() * sigma_ratio.denom(),
|
||||
total_mix_stake.u128() * sigma_ratio.numer(),
|
||||
);
|
||||
// If we can't resolve the ration we move on, and the delegation does not get rewarded
|
||||
let delegation_over_sigma_f64 =
|
||||
if let Some(f) = delegation_over_sigma_ratio.to_f64() {
|
||||
f
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let reward = Uint128(
|
||||
((1. - bond.profit_margin())
|
||||
// Need to convert this to ratio multiplication
|
||||
* (delegation.amount / sigma)
|
||||
* delegation_over_sigma_f64
|
||||
* (node_reward * price_for_uptime(uptime)))
|
||||
.max(0.);
|
||||
.max(0.) as u128,
|
||||
);
|
||||
delegation.amount += reward;
|
||||
total_rewarded += reward;
|
||||
mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?;
|
||||
mix_delegations(storage, bond.identity()).save(&delegator_address, &delegation)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,6 +655,7 @@ mod tests {
|
||||
identity_key: node_identity.clone(),
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
mixnodes(&mut storage)
|
||||
|
||||
@@ -133,6 +133,7 @@ pub mod helpers {
|
||||
Layer::One,
|
||||
12_345,
|
||||
mix_node,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::{
|
||||
ALPHA, DEFAULT_PROFIT_MARGIN, INITIAL_MIXNODE_ACTIVE_SET_SIZE,
|
||||
};
|
||||
use crate::contract::{ALPHA, INITIAL_MIXNODE_ACTIVE_SET_SIZE};
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::{calculate_epoch_reward_rate, scale_reward_by_uptime, Delegations};
|
||||
use crate::queries;
|
||||
@@ -16,8 +14,6 @@ use cosmwasm_storage::ReadonlyBucket;
|
||||
use mixnet_contract::{
|
||||
Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams,
|
||||
};
|
||||
use num::rational::Ratio;
|
||||
use num::ToPrimitive;
|
||||
|
||||
pub(crate) const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500;
|
||||
pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280;
|
||||
@@ -104,6 +100,7 @@ pub(crate) fn try_add_mixnode(
|
||||
layer,
|
||||
env.block.height,
|
||||
mix_node,
|
||||
None,
|
||||
);
|
||||
|
||||
// this might potentially require more gas if a significant number of delegations was there
|
||||
@@ -459,7 +456,6 @@ pub(crate) fn try_reward_mixnode(
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn try_reward_mixnode_v2(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
@@ -503,32 +499,32 @@ pub(crate) fn try_reward_mixnode_v2(
|
||||
/ (1. + ALPHA);
|
||||
|
||||
// Omitting the price per packet function now, it follows that base operator reward is the node_reward
|
||||
let operator_profit = ((DEFAULT_PROFIT_MARGIN
|
||||
+ (1. - DEFAULT_PROFIT_MARGIN) * (lambda / sigma))
|
||||
let operator_profit = ((current_bond.profit_margin()
|
||||
+ (1. - current_bond.profit_margin()) * (lambda / sigma))
|
||||
* (node_reward - price_for_uptime(uptime)))
|
||||
.max(0.);
|
||||
let operator_base_reward = node_reward.min(price_for_uptime(uptime));
|
||||
let total_operator_reward = (operator_base_reward + operator_profit) as u128;
|
||||
let total_operator_reward = Uint128((operator_base_reward + operator_profit) as u128);
|
||||
|
||||
let total_delegation_reward = increase_mix_delegated_stakes_v2(
|
||||
deps.storage,
|
||||
&mix_identity,
|
||||
sigma,
|
||||
¤t_bond,
|
||||
node_reward,
|
||||
uptime,
|
||||
DEFAULT_PROFIT_MARGIN,
|
||||
env.block.height
|
||||
env.block.height,
|
||||
)?;
|
||||
|
||||
// update current bond with the reward given to the node and the delegators
|
||||
// if it has been bonded for long enough
|
||||
if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height {
|
||||
node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate;
|
||||
current_bond.bond_amount.amount += node_reward;
|
||||
current_bond.bond_amount.amount += total_operator_reward;
|
||||
current_bond.total_delegation.amount += total_delegation_reward;
|
||||
mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?;
|
||||
}
|
||||
|
||||
decr_inflation_pool(total_operator_reward.u128(), deps.storage)?;
|
||||
decr_inflation_pool(total_delegation_reward.u128(), deps.storage)?;
|
||||
|
||||
Ok(Response {
|
||||
submessages: vec![],
|
||||
messages: vec![],
|
||||
@@ -1918,6 +1914,7 @@ pub mod tests {
|
||||
identity_key: node_identity.clone(),
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
mixnodes(deps.as_mut().storage)
|
||||
@@ -2017,6 +2014,7 @@ pub mod tests {
|
||||
identity_key: node_identity.clone(),
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin: None,
|
||||
};
|
||||
|
||||
mixnodes(deps.as_mut().storage)
|
||||
|
||||
@@ -38,7 +38,6 @@ fn compute_mix_rewards(mix: &MixNodeBond) {
|
||||
let work_share = one_over_k;
|
||||
let omega_k = work_share * k;
|
||||
// TODO: Use Coin struct from the Tauri wallet, this must be in the Minor denom it will be much easier then
|
||||
|
||||
}
|
||||
|
||||
// TODO:
|
||||
|
||||
@@ -61,7 +61,7 @@ pub(crate) struct MixnodeToReward {
|
||||
impl MixnodeToReward {
|
||||
/// Somewhat clumsy way of feature gatting tokenomics payments. In a tokenomics scenario this will never be None at reward time. We levarage that to Into a different ExecuteMsg variant
|
||||
fn performance(&self) -> Option<f64> {
|
||||
if cfg!(featrure = "tokenomics"){
|
||||
if cfg!(featrure = "tokenomics") {
|
||||
self.performance
|
||||
} else {
|
||||
None
|
||||
@@ -100,7 +100,7 @@ impl<'a> From<&'a MixnodeToReward> for ExecuteMsg {
|
||||
ExecuteMsg::RewardMixnodeV2 {
|
||||
identity: node.identity.clone(),
|
||||
uptime: node.uptime.u8() as u32,
|
||||
performance: perf
|
||||
performance: perf,
|
||||
}
|
||||
} else {
|
||||
ExecuteMsg::RewardMixnode {
|
||||
@@ -108,7 +108,6 @@ impl<'a> From<&'a MixnodeToReward> for ExecuteMsg {
|
||||
uptime: node.uptime.u8() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +320,6 @@ impl Rewarder {
|
||||
for mix in eligible_nodes.iter_mut() {
|
||||
mix.performance = Some(mix.uptime.u8() as f64 / total_epoch_uptime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(eligible_nodes)
|
||||
|
||||
Reference in New Issue
Block a user