From da34c2e50b49ff9a535a7dcc5c155f771fe33faa Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 26 Oct 2021 23:34:48 +0300 Subject: [PATCH] Update tokenomics --- .../validator-client/src/client.rs | 15 +- .../validator-client/src/nymd/mod.rs | 18 +- common/mixnet-contract/src/mixnode.rs | 48 +-- common/mixnet-contract/src/msg.rs | 5 +- common/network-defaults/src/lib.rs | 6 +- contracts/mixnet/src/contract.rs | 34 +-- contracts/mixnet/src/queries.rs | 21 +- contracts/mixnet/src/storage.rs | 105 ++----- contracts/mixnet/src/transactions.rs | 287 +++--------------- validator-api/src/cache/mod.rs | 32 +- validator-api/src/nymd_client.rs | 15 +- validator-api/src/rewarding/mod.rs | 18 +- 12 files changed, 135 insertions(+), 469 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 0a9d462d09..493785c90e 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -182,25 +182,18 @@ impl Client { Ok(self.nymd.get_state_params().await?) } - pub async fn get_total_mix_stake(&self) -> Result + pub async fn get_reward_pool(&self) -> Result where C: CosmWasmClient + Sync, { - Ok(self.nymd.get_total_mix_stake().await?.u128()) + Ok(self.nymd.get_reward_pool().await?.u128()) } - pub async fn get_total_gateway_stake(&self) -> Result + pub async fn get_circulating_supply(&self) -> Result where C: CosmWasmClient + Sync, { - Ok(self.nymd.get_total_gateway_stake().await?.u128()) - } - - pub async fn get_inflation_pool(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.nymd.get_inflation_pool().await?.u128()) + Ok(self.nymd.get_circulating_supply().await?.u128()) } // basically handles paging for us diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 0c1f67f782..f62727e494 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -212,31 +212,21 @@ impl NymdClient { .await } - pub async fn get_total_mix_stake(&self) -> Result + pub async fn get_reward_pool(&self) -> Result where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetTotalMixStake {}; + let request = QueryMsg::GetRewardPool {}; self.client .query_contract_smart(self.contract_address()?, &request) .await } - pub async fn get_total_gateway_stake(&self) -> Result + pub async fn get_circulating_supply(&self) -> Result where C: CosmWasmClient + Sync, { - let request = QueryMsg::GetTotalGatewayStake {}; - self.client - .query_contract_smart(self.contract_address()?, &request) - .await - } - - pub async fn get_inflation_pool(&self) -> Result - where - C: CosmWasmClient + Sync, - { - let request = QueryMsg::GetInflationPool {}; + let request = QueryMsg::GetCirculatingSupply {}; self.client .query_contract_smart(self.contract_address()?, &request) .await diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index 0004040bef..a0e2762bca 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -55,31 +55,31 @@ pub enum Layer { #[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)] pub struct NodeRewardParams { - income_global_mix: u128, + period_reward_pool: u128, k: u128, one_over_k: Decimal, total_epoch_uptime: u128, reward_blockstamp: Option, - total_mix_stake: u128, + circulating_supply: u128, uptime: u128, } impl NodeRewardParams { pub fn new( - income_global_mix: u128, + period_reward_pool: u128, k: u128, total_epoch_uptime: u128, reward_blockstamp: Option, - total_mix_stake: u128, + circulating_supply: u128, uptime: u128, ) -> NodeRewardParams { NodeRewardParams { - income_global_mix, + period_reward_pool, k, one_over_k: ratio_to_decimal(Ratio::new(1, k)), total_epoch_uptime, reward_blockstamp, - total_mix_stake, + circulating_supply, uptime, } } @@ -96,16 +96,16 @@ impl NodeRewardParams { self.reward_blockstamp = Some(blockstamp); } - pub fn income_global_mix(&self) -> u128 { - self.income_global_mix + pub fn period_reward_pool(&self) -> u128 { + self.period_reward_pool } pub fn k(&self) -> u128 { self.k } - pub fn total_mix_stake(&self) -> u128 { - self.total_mix_stake + pub fn circulating_supply(&self) -> u128 { + self.circulating_supply } pub fn reward_blockstamp(&self) -> Result { @@ -207,27 +207,28 @@ impl MixNodeBond { self.total_delegation.clone() } - pub fn bond_to_total_stake(&self, total_stake: u128) -> Ratio { - Ratio::new(self.bond_amount().amount.u128(), total_stake) + pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> Ratio { + Ratio::new(self.bond_amount().amount.u128(), circulating_supply) } - pub fn stake_to_total_stake(&self, total_stake: u128) -> Ratio { + pub fn delegations_to_circulating_supply(&self, delegations_to_circulating_supply: u128) -> Ratio { Ratio::new( - self.bond_amount().amount.u128() + self.total_delegation().amount.u128(), - total_stake, + self.total_delegation().amount.u128(), + delegations_to_circulating_supply, ) } pub fn lambda(&self, params: &NodeRewardParams) -> Ratio { // Ratio of a bond to the total stake available in the mixnet - let bond_to_total_stake_ratio = self.bond_to_total_stake(params.total_mix_stake); - bond_to_total_stake_ratio.min(params.one_over_k()) + let bond_to_circulating_supply_ratio = self.bond_to_circulating_supply(params.circulating_supply()); + bond_to_circulating_supply_ratio.min(params.one_over_k()) } pub fn sigma(&self, params: &NodeRewardParams) -> Ratio { // Ratio of a bond + delegation to the total stake available in the mixnet - let stake_to_total_stake_ratio = self.stake_to_total_stake(params.total_mix_stake); - stake_to_total_stake_ratio.min(params.one_over_k()) + let delegations_to_circulating_supply_ratio = + self.delegations_to_circulating_supply(params.circulating_supply()); + delegations_to_circulating_supply_ratio.min(params.one_over_k()) } pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult { @@ -237,7 +238,7 @@ impl MixNodeBond { let sigma = self.sigma(params); let reward = (params.performance() - * params.income_global_mix() + * params.period_reward_pool() * (sigma * omega_k + params.alpha() * lambda * (sigma * params.k))) / (Ratio::new(1u128, 1u128) + params.alpha()); @@ -271,8 +272,8 @@ impl MixNodeBond { } pub fn sigma_ratio(&self, params: &NodeRewardParams) -> Ratio { - if self.stake_to_total_stake(params.total_mix_stake) < params.one_over_k() { - self.stake_to_total_stake(params.total_mix_stake) + if self.delegations_to_circulating_supply(params.circulating_supply()) < params.one_over_k() { + self.delegations_to_circulating_supply(params.circulating_supply()) } else { params.one_over_k() } @@ -283,7 +284,8 @@ impl MixNodeBond { delegation_amount: Uint128, params: &NodeRewardParams, ) -> Result { - let scaled_delegation_amount = Ratio::new(delegation_amount.u128(), params.total_mix_stake); + let scaled_delegation_amount = + Ratio::new(delegation_amount.u128(), params.circulating_supply()); let delegator_reward = (Ratio::new(1, 1) - self.profit_margin_ratio()) * (scaled_delegation_amount / self.sigma(params)) diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index c164d0212a..90b0fdfe39 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -114,9 +114,8 @@ pub enum QueryMsg { address: Addr, }, LayerDistribution {}, - GetTotalMixStake {}, - GetTotalGatewayStake {}, - GetInflationPool {}, + GetRewardPool {}, + GetCirculatingSupply {} } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index aeaaaa0be6..b1cfad16b0 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -93,6 +93,8 @@ pub const VALIDATOR_API_VERSION: &str = "v1"; // REWARDING pub const DEFAULT_FIRST_EPOCH_START: OffsetDateTime = time::macros::datetime!(2021-08-23 12:00 UTC); -pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60); // 24h +pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60 * 30); // 30 days /// 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_OPERATOR_EPOCH_COST: u64 = 1333333; // 40$ a month at 1 Nym == 1$ +pub const DEFAULT_OPERATOR_EPOCH_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$ +// TODO: is there a way to get this from the chain +pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 22799e44e5..b4c22796b7 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -31,8 +31,9 @@ pub const INITIAL_GATEWAY_DELEGATION_REWARD_RATE: u64 = 110; pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100; pub const INITIAL_GATEWAY_ACTIVE_SET_SIZE: u32 = 20; -// Based on the PyEcon scripts and scaled down to three nodes on the QA net. -pub const INITIAL_INFLATION_POOL: u128 = 10500_00000000; +pub const REWARD_POOL: u128 = 250_000_000_000_000; +pub const EPOCH_REWARD_PERCENT: u8 = 2; // Used to calculate epoch reward pool + // 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_COST_PER_EPOCH: u32 = 40_000_000; @@ -41,6 +42,7 @@ fn default_initial_state(owner: Addr) -> State { let gateway_bond_reward_rate = Decimal::percent(INITIAL_GATEWAY_BOND_REWARD_RATE); let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE); let gateway_delegation_reward_rate = Decimal::percent(INITIAL_GATEWAY_DELEGATION_REWARD_RATE); + let epoch_reward_percent = EPOCH_REWARD_PERCENT; State { owner, @@ -217,23 +219,23 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_total_mix_stake(deps)), - QueryMsg::GetTotalGatewayStake {} => to_binary(&queries::query_total_gt_stake(deps)), - QueryMsg::GetInflationPool {} => to_binary(&queries::query_inflation_pool(deps)), + QueryMsg::GetRewardPool {} => to_binary(&queries::query_reward_pool(deps)), + QueryMsg::GetCirculatingSupply {} => to_binary(&queries::query_circulating_supply(deps)) }; Ok(query_res?) } #[entry_point] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - use crate::storage::{ - gateways_read, incr_total_gateway_stake, incr_total_mix_stake, mixnodes, PREFIX_MIXNODES, - }; - use cosmwasm_std::{Coin, Order, StdResult}; + use crate::storage::{mixnodes, PREFIX_MIXNODES}; + use cosmwasm_std::{Coin, Order}; use cosmwasm_storage::bucket_read; - use mixnet_contract::{GatewayBond, Layer, MixNodeBond}; + use mixnet_contract::{Layer, MixNodeBond}; use serde::{Deserialize, Serialize}; + // We've done the migraiton on QAnet already + return Ok(Default::default()); + #[derive(Serialize, Deserialize)] struct OldMixNodeBond { pub bond_amount: Coin, @@ -266,21 +268,9 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result, MixNodeBond)>>(); for (key, bond) in mixnode_bonds { - incr_total_mix_stake(bond.bond_amount().amount, deps.storage)?; - incr_total_mix_stake(bond.total_delegation().amount, deps.storage)?; mixnodes(deps.storage).save(&key, &bond)?; } - let gateway_bonds = gateways_read(deps.storage) - .range(None, None, Order::Ascending) - .map(|res| res.map(|item| item.1)) - .collect::>>()?; - - for bond in gateway_bonds { - incr_total_gateway_stake(bond.bond_amount().amount, deps.storage)?; - incr_total_gateway_stake(bond.total_delegation().amount, deps.storage)?; - } - Ok(Default::default()) } diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs index 32174fe56a..7c4b3a0bb9 100644 --- a/contracts/mixnet/src/queries.rs +++ b/contracts/mixnet/src/queries.rs @@ -3,13 +3,7 @@ use crate::error::ContractError; use crate::helpers::get_all_delegations_paged; -use crate::storage::{ - all_gateway_delegations_read, all_mix_delegations_read, gateway_delegations_read, - gateways_owners_read, gateways_read, inflation_pool_value, mix_delegations_read, - mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params, - reverse_gateway_delegations_read, reverse_mix_delegations_read, total_gateway_stake_value, - total_mix_stake_value, -}; +use crate::storage::{all_gateway_delegations_read, all_mix_delegations_read, circulating_supply, gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params, reverse_gateway_delegations_read, reverse_mix_delegations_read, reward_pool_value}; use config::defaults::DENOM; use cosmwasm_std::{coin, Addr, Deps, Order, StdResult, Uint128}; use mixnet_contract::{ @@ -94,16 +88,12 @@ pub(crate) fn query_layer_distribution(deps: Deps) -> LayerDistribution { read_layer_distribution(deps.storage) } -pub(crate) fn query_inflation_pool(deps: Deps) -> Uint128 { - inflation_pool_value(deps.storage) +pub(crate) fn query_reward_pool(deps: Deps) -> Uint128 { + reward_pool_value(deps.storage) } -pub(crate) fn query_total_mix_stake(deps: Deps) -> Uint128 { - total_mix_stake_value(deps.storage) -} - -pub(crate) fn query_total_gt_stake(deps: Deps) -> Uint128 { - total_gateway_stake_value(deps.storage) +pub(crate) fn query_circulating_supply(deps: Deps) -> u128 { + circulating_supply(deps.storage) } /// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm @@ -691,6 +681,7 @@ pub(crate) mod tests { gateway_delegation_reward_rate: "0.12".parse().unwrap(), mixnode_active_set_size: 1000, gateway_active_set_size: 20, + epoch_reward_percent: 2 }, mixnode_epoch_bond_reward: "1.23".parse().unwrap(), gateway_epoch_bond_reward: "4.56".parse().unwrap(), diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index 45db1eb034..842c001e3f 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -1,6 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::contract::INITIAL_INFLATION_POOL; +use crate::contract::REWARD_POOL; use crate::state::State; use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING; use crate::{error::ContractError, queries}; @@ -16,6 +16,7 @@ use mixnet_contract::{ }; use serde::de::DeserializeOwned; use serde::Serialize; +use config::defaults::TOTAL_SUPPLY; // storage prefixes // all of them must be unique and presumably not be a prefix of a different one @@ -25,10 +26,7 @@ use serde::Serialize; // singletons const CONFIG_KEY: &[u8] = b"config"; const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers"; -// Keeps total amount of stake towards mixnodes and gateways. Removing a bond removes all its delegations from the total, the reverse is true for adding a bond. -const TOTAL_MIX_STAKE_KEY: &[u8] = b"total_mn"; -const TOTAL_GATEWAY_STAKE_KEY: &[u8] = b"total_gt"; -const INFLATION_POOL_PREFIX: &[u8] = b"pool"; +const REWARD_POOL_PREFIX: &[u8] = b"pool"; // buckets pub const PREFIX_MIXNODES: &[u8] = b"mn"; @@ -53,107 +51,46 @@ pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton { singleton_read(storage, CONFIG_KEY) } -fn total_mix_stake(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, TOTAL_MIX_STAKE_KEY) +fn reward_pool(storage: &dyn Storage) -> ReadonlySingleton { + singleton_read(storage, REWARD_POOL_PREFIX) } -pub fn mut_total_mix_stake(storage: &mut dyn Storage) -> Singleton { - singleton(storage, TOTAL_MIX_STAKE_KEY) +pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton { + singleton(storage, REWARD_POOL_PREFIX) } -fn inflation_pool(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, INFLATION_POOL_PREFIX) -} - -pub fn mut_inflation_pool(storage: &mut dyn Storage) -> Singleton { - singleton(storage, INFLATION_POOL_PREFIX) -} - -fn total_gateway_stake(storage: &dyn Storage) -> ReadonlySingleton { - singleton_read(storage, TOTAL_GATEWAY_STAKE_KEY) -} - -pub fn mut_total_gateway_stake(storage: &mut dyn Storage) -> Singleton { - singleton(storage, TOTAL_GATEWAY_STAKE_KEY) -} - -pub fn total_mix_stake_value(storage: &dyn Storage) -> Uint128 { - match total_mix_stake(storage).load() { +pub fn reward_pool_value(storage: &dyn Storage) -> Uint128 { + match reward_pool(storage).load() { Ok(value) => value, - Err(_e) => Uint128(0), + Err(_e) => Uint128(REWARD_POOL), } } -pub fn total_gateway_stake_value(storage: &dyn Storage) -> Uint128 { - match total_gateway_stake(storage).load() { - Ok(value) => value, - Err(_e) => Uint128(0), - } -} - -pub fn inflation_pool_value(storage: &dyn Storage) -> Uint128 { - match inflation_pool(storage).load() { - Ok(value) => value, - Err(_e) => Uint128(INITIAL_INFLATION_POOL), - } -} - -pub fn incr_total_mix_stake( - amount: Uint128, - storage: &mut dyn Storage, -) -> Result { - let stake = total_mix_stake_value(storage).checked_add(amount)?; - mut_total_mix_stake(storage).save(&stake)?; - Ok(stake) -} - -pub fn decr_total_mix_stake( - amount: Uint128, - storage: &mut dyn Storage, -) -> Result { - let stake = total_mix_stake_value(storage).checked_sub(amount)?; - mut_total_mix_stake(storage).save(&stake)?; - Ok(stake) -} - -pub fn incr_total_gateway_stake( - amount: Uint128, - storage: &mut dyn Storage, -) -> Result { - let stake = total_gateway_stake_value(storage).checked_add(amount)?; - mut_total_gateway_stake(storage).save(&stake)?; - Ok(stake) -} - -pub fn decr_total_gateway_stake( - amount: Uint128, - storage: &mut dyn Storage, -) -> Result { - let stake = total_gateway_stake_value(storage).checked_sub(amount)?; - mut_total_gateway_stake(storage).save(&stake)?; - Ok(stake) -} - #[allow(dead_code)] -pub fn incr_inflation_pool( +pub fn incr_reward_pool( amount: Uint128, storage: &mut dyn Storage, ) -> Result { - let stake = inflation_pool_value(storage).saturating_add(amount); - mut_inflation_pool(storage).save(&stake)?; + let stake = reward_pool_value(storage).saturating_add(amount); + mut_reward_pool(storage).save(&stake)?; Ok(stake) } -pub fn decr_inflation_pool( +pub fn decr_reward_pool( amount: Uint128, storage: &mut dyn Storage, ) -> Result { // TODO: This could got to < 0 - let stake = inflation_pool_value(storage).checked_sub(amount)?; - mut_inflation_pool(storage).save(&stake)?; + let stake = reward_pool_value(storage).checked_sub(amount)?; + mut_reward_pool(storage).save(&stake)?; Ok(stake) } +pub fn circulating_supply(storage: &dyn Storage) -> u128 { + let reward_pool = reward_pool_value(storage).u128(); + TOTAL_SUPPLY - reward_pool +} + pub(crate) fn read_state_params(storage: &dyn Storage) -> StateParams { // note: In any other case, I wouldn't have attempted to unwrap this result, but in here // if we fail to load the stored state we would already be in the undefined behaviour land, diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 77972fa7a9..91dfb5596e 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -112,8 +112,6 @@ pub(crate) fn try_add_mixnode( mixnodes(deps.storage).save(identity.as_bytes(), &bond)?; mixnodes_owners(deps.storage).save(sender_bytes, identity)?; increment_layer_count(deps.storage, bond.layer)?; - incr_total_mix_stake(bond.bond_amount().amount, deps.storage)?; - incr_total_mix_stake(bond.total_delegation().amount, deps.storage)?; let attributes = vec![attr("overwritten", was_present)]; Ok(Response { @@ -153,9 +151,6 @@ pub(crate) fn try_remove_mixnode( // decrement layer count decrement_layer_count(deps.storage, mixnode_bond.layer)?; - decr_total_mix_stake(mixnode_bond.bond_amount().amount, deps.storage)?; - decr_total_mix_stake(mixnode_bond.total_delegation().amount, deps.storage)?; - // log our actions let attributes = vec![attr("action", "unbond"), attr("mixnode_bond", mixnode_bond)]; @@ -249,9 +244,6 @@ pub(crate) fn try_add_gateway( gateways_owners(deps.storage).save(sender_bytes, identity)?; increment_layer_count(deps.storage, Layer::Gateway)?; - incr_total_gateway_stake(bond.bond_amount().amount, deps.storage)?; - incr_total_gateway_stake(bond.total_delegation().amount, deps.storage)?; - let attributes = vec![attr("overwritten", was_present)]; Ok(Response { submessages: Vec::new(), @@ -290,9 +282,6 @@ pub(crate) fn try_remove_gateway( // decrement layer count decrement_layer_count(deps.storage, Layer::Gateway)?; - decr_total_gateway_stake(gateway_bond.bond_amount().amount, deps.storage)?; - decr_total_gateway_stake(gateway_bond.total_delegation().amount, deps.storage)?; - // log our actions let attributes = vec![ attr("action", "unbond"), @@ -501,10 +490,8 @@ pub(crate) fn try_reward_mixnode_v2( current_bond.bond_amount.amount += operator_reward; current_bond.total_delegation.amount += total_delegation_reward; mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?; - decr_inflation_pool(operator_reward, deps.storage)?; - decr_inflation_pool(total_delegation_reward, deps.storage)?; - incr_total_mix_stake(operator_reward, deps.storage)?; - incr_total_mix_stake(total_delegation_reward, deps.storage)?; + decr_reward_pool(operator_reward, deps.storage)?; + decr_reward_pool(total_delegation_reward, deps.storage)?; } Ok(Response { @@ -656,8 +643,6 @@ pub(crate) fn try_delegate_to_mixnode( reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?; - incr_total_mix_stake(amount, deps.storage)?; - Ok(Response::default()) } @@ -693,8 +678,6 @@ pub(crate) fn try_remove_delegation_from_mixnode( .checked_sub(delegation.amount) .unwrap(); mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?; - // We should only decr total if a node is bonded, node unbonding remove all delegations from the total - decr_total_mix_stake(delegation.amount, deps.storage)?; } Ok(Response { @@ -750,8 +733,6 @@ pub(crate) fn try_delegate_to_gateway( reverse_gateway_delegations(deps.storage, &info.sender) .save(gateway_identity.as_bytes(), &())?; - incr_total_gateway_stake(info.funds[0].amount, deps.storage)?; - Ok(Response::default()) } @@ -790,8 +771,6 @@ pub(crate) fn try_remove_delegation_from_gateway( .checked_sub(delegation.amount) .unwrap(); gateways_bucket.save(gateway_identity.as_bytes(), &existing_bond)?; - - decr_total_gateway_stake(delegation.amount, deps.storage)?; } Ok(Response { @@ -1691,6 +1670,7 @@ pub mod tests { ), mixnode_active_set_size: 42, // change something gateway_active_set_size: 5, + epoch_reward_percent: 2, }; // cannot be updated from non-owner account @@ -4090,126 +4070,9 @@ pub mod tests { assert_eq!(bob_node.layer, Layer::Two); } - #[test] - fn mix_stake_accounting() { - let mut deps = helpers::init_contract(); - let mut test_bond = INITIAL_MIXNODE_BOND; - try_add_mixnode( - deps.as_mut(), - mock_env(), - mock_info("alice", &good_mixnode_bond()), - MixNode { - identity_key: "alice".to_string(), - ..helpers::mix_node_fixture() - }, - ) - .unwrap(); - - assert_eq!(total_mix_stake_value(&deps.storage), test_bond); - - let delegation = coin(100, DENOM); - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info("bob", &vec![delegation.clone()]), - "alice".to_string(), - ) - .unwrap(); - - test_bond += Uint128(100); - assert_eq!(total_mix_stake_value(&deps.storage), test_bond); - - try_remove_mixnode(deps.as_mut(), mock_info("alice", &[])).unwrap(); - - test_bond = Uint128(0); - assert_eq!(total_mix_stake_value(&deps.storage), test_bond); - - try_add_mixnode( - deps.as_mut(), - mock_env(), - mock_info("alice", &good_mixnode_bond()), - MixNode { - identity_key: "alice".to_string(), - ..helpers::mix_node_fixture() - }, - ) - .unwrap(); - - test_bond = Uint128(100) + INITIAL_MIXNODE_BOND; - assert_eq!(total_mix_stake_value(&deps.storage), test_bond); - - try_remove_delegation_from_mixnode( - deps.as_mut(), - mock_info("bob", &[]), - "alice".to_string(), - ) - .unwrap(); - - test_bond = test_bond.checked_sub(Uint128(100)).unwrap(); - assert_eq!(total_mix_stake_value(&deps.storage), test_bond); - } - - #[test] - fn gateway_stake_accounting() { - let mut deps = helpers::init_contract(); - let mut test_bond = INITIAL_GATEWAY_BOND; - try_add_gateway( - deps.as_mut(), - mock_env(), - mock_info("alice", &good_gateway_bond()), - Gateway { - identity_key: "alice".to_string(), - ..helpers::gateway_fixture() - }, - ) - .unwrap(); - - assert_eq!(total_gateway_stake_value(&deps.storage), test_bond); - - let delegation = coin(100, DENOM); - try_delegate_to_gateway( - deps.as_mut(), - mock_env(), - mock_info("bob", &vec![delegation.clone()]), - "alice".to_string(), - ) - .unwrap(); - - test_bond += Uint128(100); - assert_eq!(total_gateway_stake_value(&deps.storage), test_bond); - - try_remove_gateway(deps.as_mut(), mock_info("alice", &[])).unwrap(); - - test_bond = Uint128(0); - assert_eq!(total_gateway_stake_value(&deps.storage), test_bond); - - try_add_gateway( - deps.as_mut(), - mock_env(), - mock_info("alice", &good_gateway_bond()), - Gateway { - identity_key: "alice".to_string(), - ..helpers::gateway_fixture() - }, - ) - .unwrap(); - - test_bond = Uint128(100) + INITIAL_GATEWAY_BOND; - assert_eq!(total_gateway_stake_value(&deps.storage), test_bond); - - try_remove_delegation_from_gateway( - deps.as_mut(), - mock_info("bob", &[]), - "alice".to_string(), - ) - .unwrap(); - - test_bond = test_bond.checked_sub(Uint128(100)).unwrap(); - assert_eq!(total_gateway_stake_value(&deps.storage), test_bond); - } - #[test] fn test_tokenomics_rewarding() { + use crate::contract::{EPOCH_REWARD_PERCENT, REWARD_POOL}; use num::rational::Ratio; use std::str::FromStr; @@ -4217,11 +4080,14 @@ pub mod tests { let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; - let income_global_mix = 17500_000000; - let k = 5; - mut_inflation_pool(deps.as_mut().storage) - .save(&Uint128(income_global_mix)) - .unwrap(); + let period_reward_pool = (REWARD_POOL / 100) * EPOCH_REWARD_PERCENT as u128; + assert_eq!(period_reward_pool, 5_000_000_000_000); + let k = 200; // Imagining our active set size is 200 + let circulating_supply = circulating_supply(&deps.storage); + assert_eq!(circulating_supply, 750_000_000_000_000u128); + // mut_reward_pool(deps.as_mut().storage) + // .save(&Uint128(period_reward_pool)) + // .unwrap(); try_add_mixnode( deps.as_mut(), @@ -4234,170 +4100,99 @@ pub mod tests { ) .unwrap(); - try_add_mixnode( + try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("mix_1", &good_mixnode_bond()), - MixNode { - identity_key: "mix_1".to_string(), - ..helpers::mix_node_fixture() - }, - ) - .unwrap(); - - try_add_mixnode( - deps.as_mut(), - mock_env(), - mock_info("mix_2", &good_mixnode_bond()), - MixNode { - identity_key: "mix_2".to_string(), - ..helpers::mix_node_fixture() - }, - ) - .unwrap(); - - try_add_mixnode( - deps.as_mut(), - mock_env(), - mock_info("mix_3", &good_mixnode_bond()), - MixNode { - identity_key: "mix_3".to_string(), - ..helpers::mix_node_fixture() - }, - ) - .unwrap(); - - try_add_mixnode( - deps.as_mut(), - mock_env(), - mock_info("mix_4", &good_mixnode_bond()), - MixNode { - identity_key: "mix_4".to_string(), - ..helpers::mix_node_fixture() - }, + mock_info("d1", &vec![coin(10_000000, DENOM)]), + "alice".to_string(), ) .unwrap(); try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("d1.mix_1", &vec![coin(10_000000, DENOM)]), - "mix_1".to_string(), + mock_info("d2", &vec![coin(20_000000, DENOM)]), + "alice".to_string(), ) .unwrap(); - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info("d2.mix_1", &vec![coin(20_000000, DENOM)]), - "mix_1".to_string(), - ) - .unwrap(); - - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info("d1.mix_2", &vec![coin(10_000000, DENOM)]), - "mix_2".to_string(), - ) - .unwrap(); - - try_delegate_to_mixnode( - deps.as_mut(), - mock_env(), - mock_info("d2.mix_2", &vec![coin(10_000000, DENOM)]), - "mix_2".to_string(), - ) - .unwrap(); - - assert_eq!(total_mix_stake_value(&deps.storage).u128(), 550_000000); - - let total_mix_stake = total_mix_stake_value(&deps.storage); - - let total_uptime = 380; + let total_uptime = 200_000; let info = mock_info(network_monitor_address.as_ref(), &[]); env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; - let mix_1 = mixnodes_read(&deps.storage).load(b"mix_1").unwrap(); + let mix_1 = mixnodes_read(&deps.storage).load(b"alice").unwrap(); let mix_1_uptime = 100; let _mix_1_performance = Decimal::from_ratio(mix_1_uptime, total_uptime); let mut params = NodeRewardParams::new( - income_global_mix, + period_reward_pool, k, total_uptime, None, - total_mix_stake.u128(), + circulating_supply, mix_1_uptime, ); params.set_reward_blockstamp(env.block.height); - assert_eq!(params.performance(), Ratio::new(5, 19)); + assert_eq!(params.performance(), Ratio::new(1, 2000)); let mix_1_reward_result = mix_1.reward(¶ms); assert_eq!( mix_1_reward_result.sigma(), - Decimal::from_str("0.2").unwrap() + Decimal::from_str("0.000000040000000000").unwrap() ); assert_eq!( mix_1_reward_result.lambda(), - Decimal::from_str("0.181818181818181818").unwrap() + Decimal::from_str("0.000000133333333333").unwrap() ); assert_eq!( mix_1_reward_result.reward(), - Decimal::from_str("901729849.098270150901729849").unwrap() + Decimal::from_str("76.923692307692307692").unwrap() ); let mix1_operator_profit = mix_1.operator_reward(¶ms).unwrap(); let mix1_delegator1_reward = mix_1 - .reward_delegation(Uint128(10_000000), ¶ms) + .reward_delegation(Uint128(10_000_000), ¶ms) .unwrap(); let mix1_delegator2_reward = mix_1 - .reward_delegation(Uint128(20_000000), ¶ms) + .reward_delegation(Uint128(20_000_000), ¶ms) .unwrap(); - assert_eq!(mix1_operator_profit, 827951952); - assert_eq!(mix1_delegator1_reward, 73777896); - assert_eq!(mix1_delegator2_reward, 147555793); + // These might be generalizable over percentages, instead of exact values + assert_eq!(mix1_operator_profit, 238); + assert_eq!(mix1_delegator1_reward, 23); + assert_eq!(mix1_delegator2_reward, 46); - let pre_reward_bond = read_mixnode_bond(&deps.storage, b"mix_1").unwrap().u128(); - assert_eq!(pre_reward_bond, 100_000000); + let pre_reward_bond = read_mixnode_bond(&deps.storage, b"alice").unwrap().u128(); + assert_eq!(pre_reward_bond, 100_000_000); - let pre_reward_delegation = read_mixnode_delegation(&deps.storage, b"mix_1") + let pre_reward_delegation = read_mixnode_delegation(&deps.storage, b"alice") .unwrap() .u128(); - assert_eq!(pre_reward_delegation, 30_000000); + assert_eq!(pre_reward_delegation, 30_000_000); - try_reward_mixnode_v2(deps.as_mut(), env, info, "mix_1".to_string(), params).unwrap(); + try_reward_mixnode_v2(deps.as_mut(), env, info, "alice".to_string(), params).unwrap(); assert_eq!( - read_mixnode_bond(&deps.storage, b"mix_1").unwrap().u128(), + read_mixnode_bond(&deps.storage, b"alice").unwrap().u128(), pre_reward_bond + mix1_operator_profit + params.operator_cost().to_u128().unwrap() ); assert_eq!( - read_mixnode_delegation(&deps.storage, b"mix_1") + read_mixnode_delegation(&deps.storage, b"alice") .unwrap() .u128(), - 251333689 - ); - assert_eq!( - total_mix_stake_value(&deps.storage).u128(), - total_mix_stake.u128() - + mix1_operator_profit as u128 - + params.operator_cost().to_u128().unwrap() - + mix1_delegator1_reward - + mix1_delegator2_reward + pre_reward_delegation + mix1_delegator1_reward + mix1_delegator2_reward ); assert_eq!( - inflation_pool_value(&deps.storage).u128(), - income_global_mix + reward_pool_value(&deps.storage).u128(), + REWARD_POOL - (mix1_operator_profit as u128 + params.operator_cost().to_u128().unwrap() + mix1_delegator1_reward diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index 017e66ffed..3a787f2cdf 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -37,9 +37,6 @@ struct ValidatorCacheInner { current_mixnode_active_set_size: AtomicUsize, current_gateway_active_set_size: AtomicUsize, - - total_mix_stake: RwLock>, - total_gateway_stake: RwLock>, } #[derive(Default, Serialize, Clone)] @@ -79,30 +76,21 @@ impl ValidatorCacheRefresher { where C: CosmWasmClient + Sync, { - let (mixnodes, gateways, total_mix_stake, total_gt_state) = tokio::try_join!( + let (mixnodes, gateways) = tokio::try_join!( self.nymd_client.get_mixnodes(), self.nymd_client.get_gateways(), - self.nymd_client.get_total_mix_stake(), - self.nymd_client.get_total_gateway_stake() )?; let state_params = self.nymd_client.get_state_params().await?; info!( - "Updating validator cache. There are {} mixnodes and {} gateways, total_mix_stake is {}", + "Updating validator cache. There are {} mixnodes and {} gateways", mixnodes.len(), gateways.len(), - total_mix_stake ); self.cache - .update_cache( - mixnodes, - gateways, - total_mix_stake, - total_gt_state, - state_params, - ) + .update_cache(mixnodes, gateways, state_params) .await; Ok(()) @@ -189,8 +177,6 @@ impl ValidatorCache { &self, mut mixnodes: Vec, mut gateways: Vec, - total_mix_stake: u128, - total_gt_stake: u128, state: StateParams, ) { // if our data is valid, it means the active sets are available, @@ -232,16 +218,6 @@ impl ValidatorCache { self.inner.mixnodes.write().await.set(mixnodes); self.inner.gateways.write().await.set(gateways); - self.inner - .total_mix_stake - .write() - .await - .set(total_mix_stake); - self.inner - .total_gateway_stake - .write() - .await - .set(total_gt_stake); } pub async fn mixnodes(&self) -> Cache> { @@ -327,8 +303,6 @@ impl ValidatorCacheInner { active_gateways_available: AtomicBool::new(false), current_mixnode_active_set_size: Default::default(), current_gateway_active_set_size: Default::default(), - total_mix_stake: Default::default(), - total_gateway_stake: Default::default(), } } } diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index fed9fd0991..cd42c1af86 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -94,25 +94,18 @@ impl Client { Ok(time) } - pub(crate) async fn get_total_mix_stake(&self) -> Result + pub(crate) async fn get_reward_pool(&self) -> Result where C: CosmWasmClient + Sync, { - Ok(self.0.read().await.get_total_mix_stake().await?) + Ok(self.0.read().await.get_reward_pool().await?) } - pub(crate) async fn get_total_gateway_stake(&self) -> Result + pub(crate) async fn get_circulating_supply(&self) -> Result where C: CosmWasmClient + Sync, { - Ok(self.0.read().await.get_total_gateway_stake().await?) - } - - pub(crate) async fn get_inflation_pool(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.0.read().await.get_inflation_pool().await?) + Ok(self.0.read().await.get_circulating_supply().await?) } pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError> diff --git a/validator-api/src/rewarding/mod.rs b/validator-api/src/rewarding/mod.rs index cf75ffc9fa..be06203dfd 100644 --- a/validator-api/src/rewarding/mod.rs +++ b/validator-api/src/rewarding/mod.rs @@ -291,13 +291,13 @@ impl Rewarder { // by people hesitating to delegate to nodes without them and thus those nodes disappearing // from the active set (once introduced) let mixnode_delegators = self.produce_active_mixnode_delegators_map().await?; - let total_mix_stake = self.nymd_client.get_total_mix_stake().await?; - let inflation_pool = self.nymd_client.get_inflation_pool().await?; - let k = self - .nymd_client - .get_state_params() - .await? - .mixnode_active_set_size; + // TODO: get node saturation + let reward_pool = self.nymd_client.get_reward_pool().await?; + let circulating_supply = self.nymd_client.get_circulating_supply().await?; + let state = self.nymd_client.get_state_params().await?; + let k = state.mixnode_active_set_size; + let period_reward_pool = (reward_pool / 100) * 2_u128; + // 1. go through all active mixnodes // 2. filter out nodes that are currently not in the active set (as `mixnode_delegators` was obtained by // querying the validator) @@ -325,11 +325,11 @@ impl Rewarder { if cfg!(feature = "tokenomics") { for mix in eligible_nodes.iter_mut() { mix.params = Some(NodeRewardParams::new( - inflation_pool, + period_reward_pool, k.into(), total_epoch_uptime, None, - total_mix_stake, + circulating_supply, mix.uptime.u8().into(), )) }