Finalize implementation

Fix reciprocal

Fix, fix reciprocal

cargo fmt
This commit is contained in:
Drazen Urch
2021-10-05 09:46:17 +02:00
parent b47dad1b7d
commit c4bd5e04a5
10 changed files with 103 additions and 53 deletions
+2 -2
View File
@@ -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),
}
+1 -1
View File
@@ -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::{
+35 -8
View File
@@ -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:
+1 -1
View File
@@ -49,7 +49,7 @@ pub enum ExecuteMsg {
identity: IdentityKey,
// percentage value in range 0-100
uptime: u32,
performance: f64
performance: f64,
},
RewardGateway {
+7 -6
View File
@@ -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())
+41 -15
View File
@@ -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)
+1
View File
@@ -133,6 +133,7 @@ pub mod helpers {
Layer::One,
12_345,
mix_node,
None,
)
}
+13 -15
View File
@@ -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,
&current_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(), &current_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)
-1
View File
@@ -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:
+2 -4
View File
@@ -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)