Update tokenomics

This commit is contained in:
Drazen Urch
2021-10-26 23:34:48 +03:00
committed by Drazen Urch
parent 0630c4c240
commit da34c2e50b
12 changed files with 135 additions and 469 deletions
@@ -182,25 +182,18 @@ impl<C> Client<C> {
Ok(self.nymd.get_state_params().await?)
}
pub async fn get_total_mix_stake(&self) -> Result<u128, ValidatorClientError>
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
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<u128, ValidatorClientError>
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_total_gateway_stake().await?.u128())
}
pub async fn get_inflation_pool(&self) -> Result<u128, ValidatorClientError>
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
@@ -212,31 +212,21 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_total_mix_stake(&self) -> Result<Uint128, NymdError>
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
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<Uint128, NymdError>
pub async fn get_circulating_supply(&self) -> Result<Uint128, NymdError>
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<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetInflationPool {};
let request = QueryMsg::GetCirculatingSupply {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
+25 -23
View File
@@ -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<u64>,
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<u64>,
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<u64, MixnetContractError> {
@@ -207,27 +207,28 @@ impl MixNodeBond {
self.total_delegation.clone()
}
pub fn bond_to_total_stake(&self, total_stake: u128) -> Ratio<u128> {
Ratio::new(self.bond_amount().amount.u128(), total_stake)
pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> Ratio<u128> {
Ratio::new(self.bond_amount().amount.u128(), circulating_supply)
}
pub fn stake_to_total_stake(&self, total_stake: u128) -> Ratio<u128> {
pub fn delegations_to_circulating_supply(&self, delegations_to_circulating_supply: u128) -> Ratio<u128> {
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<u128> {
// 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<u128> {
// 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<u128> {
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<u128, MixnetContractError> {
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))
+2 -3
View File
@@ -114,9 +114,8 @@ pub enum QueryMsg {
address: Addr,
},
LayerDistribution {},
GetTotalMixStake {},
GetTotalGatewayStake {},
GetInflationPool {},
GetRewardPool {},
GetCirculatingSupply {}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+4 -2
View File
@@ -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;
+12 -22
View File
@@ -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<QueryResponse, Cont
gateway_identity,
address,
)?),
QueryMsg::GetTotalMixStake {} => 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<Response, ContractError> {
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<Response, C
.collect::<Vec<(Vec<u8>, 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::<StdResult<Vec<GatewayBond>>>()?;
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())
}
+6 -15
View File
@@ -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(),
+21 -84
View File
@@ -1,6 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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<State> {
singleton_read(storage, CONFIG_KEY)
}
fn total_mix_stake(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
singleton_read(storage, TOTAL_MIX_STAKE_KEY)
fn reward_pool(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
singleton_read(storage, REWARD_POOL_PREFIX)
}
pub fn mut_total_mix_stake(storage: &mut dyn Storage) -> Singleton<Uint128> {
singleton(storage, TOTAL_MIX_STAKE_KEY)
pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton<Uint128> {
singleton(storage, REWARD_POOL_PREFIX)
}
fn inflation_pool(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
singleton_read(storage, INFLATION_POOL_PREFIX)
}
pub fn mut_inflation_pool(storage: &mut dyn Storage) -> Singleton<Uint128> {
singleton(storage, INFLATION_POOL_PREFIX)
}
fn total_gateway_stake(storage: &dyn Storage) -> ReadonlySingleton<Uint128> {
singleton_read(storage, TOTAL_GATEWAY_STAKE_KEY)
}
pub fn mut_total_gateway_stake(storage: &mut dyn Storage) -> Singleton<Uint128> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
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<Uint128, ContractError> {
// 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,
+41 -246
View File
@@ -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(), &current_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(&params);
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(&params).unwrap();
let mix1_delegator1_reward = mix_1
.reward_delegation(Uint128(10_000000), &params)
.reward_delegation(Uint128(10_000_000), &params)
.unwrap();
let mix1_delegator2_reward = mix_1
.reward_delegation(Uint128(20_000000), &params)
.reward_delegation(Uint128(20_000_000), &params)
.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
+3 -29
View File
@@ -37,9 +37,6 @@ struct ValidatorCacheInner {
current_mixnode_active_set_size: AtomicUsize,
current_gateway_active_set_size: AtomicUsize,
total_mix_stake: RwLock<Cache<u128>>,
total_gateway_stake: RwLock<Cache<u128>>,
}
#[derive(Default, Serialize, Clone)]
@@ -79,30 +76,21 @@ impl<C> ValidatorCacheRefresher<C> {
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<MixNodeBond>,
mut gateways: Vec<GatewayBond>,
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<Vec<MixNodeBond>> {
@@ -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(),
}
}
}
+4 -11
View File
@@ -94,25 +94,18 @@ impl<C> Client<C> {
Ok(time)
}
pub(crate) async fn get_total_mix_stake(&self) -> Result<u128, ValidatorClientError>
pub(crate) async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
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<u128, ValidatorClientError>
pub(crate) async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.0.read().await.get_total_gateway_stake().await?)
}
pub(crate) async fn get_inflation_pool(&self) -> Result<u128, ValidatorClientError>
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<Vec<MixNodeBond>, ValidatorClientError>
+9 -9
View File
@@ -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(),
))
}