diff --git a/.gitignore b/.gitignore
index ac7542786c..32512e37ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,8 @@ validator-api/v4.json
validator-api/v6.json
**/node_modules
validator-api/keypair
+contracts/mixnet/code_id
+contracts/mixnet/Justfile
+contracts/mixnet/Makefile
+validator-config
+*.patch
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 034a979e0f..ee1f369724 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -228,6 +228,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+[[package]]
+name = "az"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
+
[[package]]
name = "base-x"
version = "0.2.8"
@@ -411,6 +417,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytemuck"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -1642,6 +1654,18 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "fixed"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
+dependencies = [
+ "az",
+ "bytemuck",
+ "half",
+ "typenum",
+]
+
[[package]]
name = "flate2"
version = "1.0.22"
@@ -2271,6 +2295,12 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
[[package]]
name = "handlebars"
version = "3.5.5"
@@ -2986,10 +3016,15 @@ dependencies = [
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
+ "az",
"cosmwasm-std",
+ "fixed",
+ "log",
+ "network-defaults",
"schemars",
"serde",
"serde_repr",
+ "thiserror",
"ts-rs",
]
diff --git a/Cargo.toml b/Cargo.toml
index 02ceec5e95..8d959736a0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,4 +62,4 @@ default-members = [
"validator-api",
]
-exclude = ["explorer", "contracts"]
+exclude = ["explorer", "contracts", "tokenomics-py"]
diff --git a/README.md b/README.md
index 155ebc01ea..cd149437b8 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,6 @@ SPDX-License-Identifier: Apache-2.0
## The Nym Privacy Platform
-This repository contains the Nym mixnet.
-
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
@@ -33,6 +31,45 @@ There's a `.env.sample-dev` file provided which you can rename to `.env` if you
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
+### Rewards
+
+Node, node operator and delegator rewards are determined according to the principles laid out in the section 6 of [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf). Below is a TLDR of the variables and formulas involved in calculating the epoch rewards. Initial reward pool is set to 250 million Nym, making the circulating supply 750 million Nym.
+
+|Symbol|Definition|
+|---|---|
+|
|global share of rewards available, starts at 2% of the reward pool.
+|
|node reward for mixnode `i`.
+|
|ratio of total node stake (node bond + all delegations) to the token circulating supply.
+|
|ratio of stake operator has plaged to their node to the token circulating supply.
+|
|fraction of total effort undertaken by node `i`, set to `1/k` in testnet Milhon.
+|
|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `active set` size, and set to 5000 in testnet Milhon.
+|
|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
+|
|declared profit margin of operator `i`, defaults to 10% in testnet Milhon.
+|
|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
+|
|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 Nym for testnet Milhon.
+
+Node reward for node `i` is determined as:
+
+
+
+where:
+
+
+
+and
+
+
+
+Operator of node `i` is credited with the following amount:
+
+
+
+Delegate with stake `s` recieves:
+
+
+
+where `s'` is stake `s` scaled over total token circulating supply.
+
### Licensing and copyright information
This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files.
diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs
index da9fee7bd2..487280157e 100644
--- a/common/client-libs/gateway-client/src/client.rs
+++ b/common/client-libs/gateway-client/src/client.rs
@@ -523,8 +523,11 @@ impl GatewayClient {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
- if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
- return Err(GatewayClientError::NotEnoughBandwidth);
+ if self.bandwidth_remaining <= self.estimate_required_bandwidth(&packets) {
+ return Err(GatewayClientError::NotEnoughBandwidth((
+ self.estimate_required_bandwidth(&packets),
+ self.bandwidth_remaining,
+ )));
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -592,7 +595,10 @@ impl GatewayClient {
}
#[cfg(feature = "coconut")]
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
- return Err(GatewayClientError::NotEnoughBandwidth);
+ return Err(GatewayClientError::NotEnoughBandwidth((
+ (mix_packet.sphinx_packet().len() as i64),
+ self.bandwidth_remaining,
+ )));
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -631,7 +637,10 @@ impl GatewayClient {
}
#[cfg(feature = "coconut")]
if self.bandwidth_remaining <= 0 {
- return Err(GatewayClientError::NotEnoughBandwidth);
+ return Err(GatewayClientError::NotEnoughBandwidth((
+ 0,
+ self.bandwidth_remaining,
+ )));
}
if self.connection.is_partially_delegated() {
return Ok(());
diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs
index 170314e484..90cd77300d 100644
--- a/common/client-libs/gateway-client/src/error.rs
+++ b/common/client-libs/gateway-client/src/error.rs
@@ -23,7 +23,7 @@ pub enum GatewayClientError {
MalformedResponse,
SerializeCredential,
NotAuthenticated,
- NotEnoughBandwidth,
+ NotEnoughBandwidth((i64, i64)),
UnexpectedResponse,
ConnectionInInvalidState,
RegistrationFailure(HandshakeError),
@@ -102,8 +102,12 @@ impl fmt::Display for GatewayClientError {
write!(f, "gateway returned an error response - {}", err)
}
GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"),
- GatewayClientError::NotEnoughBandwidth => {
- write!(f, "client does not have enough bandwidth")
+ GatewayClientError::NotEnoughBandwidth((estimated, remaining)) => {
+ write!(
+ f,
+ "Client does not have enough bandwidth: estimated {}, remaining: {}",
+ estimated, remaining
+ )
}
GatewayClientError::SerializeCredential => {
write!(f, "credential could not be serialized")
diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs
index b82e9907b3..9fba57208f 100644
--- a/common/client-libs/gateway-client/src/socket_state.rs
+++ b/common/client-libs/gateway-client/src/socket_state.rs
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
}
.is_err()
{
- panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
+ warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
}
};
diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs
index 0e2533b37d..5312e248c0 100644
--- a/common/client-libs/validator-client/src/client.rs
+++ b/common/client-libs/validator-client/src/client.rs
@@ -172,6 +172,34 @@ impl Client {
Ok(self.nymd.get_state_params().await?)
}
+ pub async fn get_reward_pool(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_reward_pool().await?.u128())
+ }
+
+ pub async fn get_circulating_supply(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_circulating_supply().await?.u128())
+ }
+
+ pub async fn get_sybil_resistance_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_sybil_resistance_percent().await?)
+ }
+
+ pub async fn get_epoch_reward_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.nymd.get_epoch_reward_percent().await?)
+ }
+
// basically handles paging for us
pub async fn get_all_nymd_mixnodes(&self) -> Result, ValidatorClientError>
where
diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs
index 54477ca013..7fc1685849 100644
--- a/common/client-libs/validator-client/src/nymd/mod.rs
+++ b/common/client-libs/validator-client/src/nymd/mod.rs
@@ -11,7 +11,7 @@ use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
-use cosmwasm_std::Coin;
+use cosmwasm_std::{Coin, Uint128};
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
@@ -211,6 +211,46 @@ impl NymdClient {
.await
}
+ pub async fn get_reward_pool(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ let request = QueryMsg::GetRewardPool {};
+ self.client
+ .query_contract_smart(self.contract_address()?, &request)
+ .await
+ }
+
+ pub async fn get_circulating_supply(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ let request = QueryMsg::GetCirculatingSupply {};
+ self.client
+ .query_contract_smart(self.contract_address()?, &request)
+ .await
+ }
+
+ pub async fn get_sybil_resistance_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ let request = QueryMsg::GetSybilResistancePercent {};
+ self.client
+ .query_contract_smart(self.contract_address()?, &request)
+ .await
+ }
+
+ pub async fn get_epoch_reward_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ let request = QueryMsg::GetEpochRewardPercent {};
+ self.client
+ .query_contract_smart(self.contract_address()?, &request)
+ .await
+ }
+
/// Checks whether there is a bonded mixnode associated with the provided client's address
pub async fn owns_mixnode(&self, address: &AccountId) -> Result
where
diff --git a/common/mixnet-contract/Cargo.toml b/common/mixnet-contract/Cargo.toml
index 67c94e3355..fba7fecfd1 100644
--- a/common/mixnet-contract/Cargo.toml
+++ b/common/mixnet-contract/Cargo.toml
@@ -8,10 +8,18 @@ edition = "2018"
[dependencies]
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
-cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256" }
+cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
#cosmwasm-std = { version = "0.14.1" }
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
-ts-rs = "3.0"
+ts-rs = { version = "3.0", optional = true }
+thiserror = "1.0"
+network-defaults = { path = "../network-defaults" }
+fixed = "1.1"
+az = "1.1"
+log = "0.4.14"
+
+[features]
+default = ["ts-rs"]
diff --git a/common/mixnet-contract/src/error.rs b/common/mixnet-contract/src/error.rs
new file mode 100644
index 0000000000..b338d12125
--- /dev/null
+++ b/common/mixnet-contract/src/error.rs
@@ -0,0 +1,9 @@
+use thiserror::Error;
+
+#[derive(Error, Debug, PartialEq)]
+pub enum MixnetContractError {
+ #[error("Overflow Error")]
+ OverflowError(#[from] cosmwasm_std::OverflowError),
+ #[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
+ BlockstampNotSet,
+}
diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs
index 25c9fe0bab..23ab18b11d 100644
--- a/common/mixnet-contract/src/gateway.rs
+++ b/common/mixnet-contract/src/gateway.rs
@@ -7,9 +7,9 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
-use ts_rs::TS;
-#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
+#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
+#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub struct Gateway {
pub host: String,
pub mix_port: u16,
diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs
index ffecdea57c..04c758ec29 100644
--- a/common/mixnet-contract/src/lib.rs
+++ b/common/mixnet-contract/src/lib.rs
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
mod delegation;
+pub mod error;
mod gateway;
-mod mixnode;
+pub mod mixnode;
mod msg;
mod types;
diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs
index f6b3281580..82d8b4c33c 100644
--- a/common/mixnet-contract/src/mixnode.rs
+++ b/common/mixnet-contract/src/mixnode.rs
@@ -2,15 +2,20 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
-use cosmwasm_std::{coin, Addr, Coin};
+use az::CheckedCast;
+use cosmwasm_std::{coin, Addr, Coin, Uint128};
+use log::error;
+use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
-use ts_rs::TS;
-#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
+type U128 = fixed::types::U75F53; // u128 with 18 significant digits
+
+#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
+#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub struct MixNode {
pub host: String,
pub mix_port: u16,
@@ -43,6 +48,97 @@ pub enum Layer {
Three = 3,
}
+#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
+pub struct NodeRewardParams {
+ period_reward_pool: Uint128,
+ k: Uint128,
+ reward_blockstamp: u64,
+ circulating_supply: Uint128,
+ uptime: Uint128,
+ sybil_resistance_percent: u8,
+}
+
+impl NodeRewardParams {
+ pub fn new(
+ period_reward_pool: u128,
+ k: u128,
+ reward_blockstamp: u64,
+ circulating_supply: u128,
+ uptime: u128,
+ sybil_resistance_percent: u8,
+ ) -> NodeRewardParams {
+ NodeRewardParams {
+ period_reward_pool: Uint128(period_reward_pool),
+ k: Uint128(k),
+ reward_blockstamp,
+ circulating_supply: Uint128(circulating_supply),
+ uptime: Uint128(uptime),
+ sybil_resistance_percent,
+ }
+ }
+
+ pub fn performance(&self) -> U128 {
+ U128::from_num(self.uptime.u128()) / U128::from_num(100)
+ }
+
+ pub fn operator_cost(&self) -> U128 {
+ U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_EPOCH_COST as u128)
+ }
+
+ pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
+ self.reward_blockstamp = blockstamp;
+ }
+
+ pub fn period_reward_pool(&self) -> u128 {
+ self.period_reward_pool.u128()
+ }
+
+ pub fn k(&self) -> u128 {
+ self.k.u128()
+ }
+
+ pub fn circulating_supply(&self) -> u128 {
+ self.circulating_supply.u128()
+ }
+
+ pub fn reward_blockstamp(&self) -> u64 {
+ self.reward_blockstamp
+ }
+
+ pub fn uptime(&self) -> u128 {
+ self.uptime.u128()
+ }
+
+ pub fn one_over_k(&self) -> U128 {
+ U128::from_num(1) / U128::from_num(self.k.u128())
+ }
+
+ pub fn alpha(&self) -> U128 {
+ U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
+ }
+}
+
+#[derive(Debug)]
+pub struct NodeRewardResult {
+ reward: U128,
+ lambda: U128,
+ sigma: U128,
+}
+
+impl NodeRewardResult {
+ pub fn reward(&self) -> U128 {
+ self.reward
+ }
+
+ pub fn lambda(&self) -> U128 {
+ self.lambda
+ }
+
+ pub fn sigma(&self) -> U128 {
+ self.sigma
+ }
+}
+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub bond_amount: Coin,
@@ -51,6 +147,7 @@ pub struct MixNodeBond {
pub layer: Layer,
pub block_height: u64,
pub mix_node: MixNode,
+ pub profit_margin_percent: Option,
}
impl MixNodeBond {
@@ -60,6 +157,7 @@ impl MixNodeBond {
layer: Layer,
block_height: u64,
mix_node: MixNode,
+ profit_margin_percent: Option,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &bond_amount.denom),
@@ -68,9 +166,15 @@ impl MixNodeBond {
layer,
block_height,
mix_node,
+ profit_margin_percent,
}
}
+ pub fn profit_margin(&self) -> U128 {
+ U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
+ / U128::from_num(100)
+ }
+
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
@@ -86,6 +190,116 @@ impl MixNodeBond {
pub fn mix_node(&self) -> &MixNode {
&self.mix_node
}
+
+ pub fn total_delegation(&self) -> Coin {
+ self.total_delegation.clone()
+ }
+
+ pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
+ U128::from_num(self.bond_amount().amount.u128()) / U128::from_num(circulating_supply)
+ }
+
+ pub fn total_stake_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
+ U128::from_num(self.bond_amount().amount.u128() + self.total_delegation().amount.u128())
+ / U128::from_num(circulating_supply)
+ }
+
+ pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
+ // Ratio of a bond to the token circulating supply
+ 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) -> U128 {
+ // Ratio of a delegation to the the token circulating supply
+ let total_stake_to_circulating_supply_ratio =
+ self.total_stake_to_circulating_supply(params.circulating_supply());
+ total_stake_to_circulating_supply_ratio.min(params.one_over_k())
+ }
+
+ pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
+ // Assuming uniform work distribution across the network this is one_over_k * k
+ let omega_k = U128::from_num(1u128);
+ let lambda = self.lambda(params);
+ let sigma = self.sigma(params);
+
+ let reward = params.performance()
+ * params.period_reward_pool()
+ * (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
+ / (U128::from_num(1) + params.alpha());
+
+ NodeRewardResult {
+ reward,
+ lambda,
+ sigma,
+ }
+ }
+
+ pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
+ if self.reward(params).reward() < params.operator_cost() {
+ U128::from_num(0)
+ } else {
+ self.reward(params).reward() - params.operator_cost()
+ }
+ }
+
+ pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
+ let reward = self.reward(params);
+ let profit = if reward.reward < params.operator_cost() {
+ U128::from_num(0)
+ } else {
+ reward.reward - params.operator_cost()
+ };
+ let operator_base_reward = reward.reward.min(params.operator_cost());
+ let operator_reward = (self.profit_margin()
+ + (U128::from_num(1) - self.profit_margin()) * reward.lambda / reward.sigma)
+ * profit;
+
+ let reward = (operator_reward + operator_base_reward).max(U128::from_num(0));
+
+ if let Some(int_reward) = reward.checked_cast() {
+ int_reward
+ } else {
+ error!(
+ "Could not cast reward ({}) to u128, returning 0 - mixnode {}",
+ reward,
+ self.identity()
+ );
+ 0u128
+ }
+ }
+
+ pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
+ if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
+ {
+ self.total_stake_to_circulating_supply(params.circulating_supply())
+ } else {
+ params.one_over_k()
+ }
+ }
+
+ pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
+ let scaled_delegation_amount =
+ U128::from_num(delegation_amount.u128()) / U128::from_num(params.circulating_supply());
+
+ let delegator_reward = (U128::from_num(1) - self.profit_margin())
+ * scaled_delegation_amount
+ / self.sigma(params)
+ * self.node_profit(params);
+
+ let reward = delegator_reward.max(U128::from_num(0));
+ if let Some(int_reward) = reward.checked_cast() {
+ int_reward
+ } else {
+ error!(
+ "Could not cast delegator reward ({}) to u128, returning 0 - mixnode {}",
+ reward,
+ self.identity()
+ );
+ 0u128
+ }
+ }
}
impl PartialOrd for MixNodeBond {
@@ -217,6 +431,7 @@ mod tests {
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
+ profit_margin_percent: Some(10),
};
let mix2 = MixNodeBond {
@@ -226,6 +441,7 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
+ profit_margin_percent: Some(10),
};
let mix3 = MixNodeBond {
@@ -235,6 +451,7 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
+ profit_margin_percent: Some(10),
};
let mix4 = MixNodeBond {
@@ -244,6 +461,7 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
+ profit_margin_percent: Some(10),
};
let mix5 = MixNodeBond {
@@ -253,6 +471,7 @@ mod tests {
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
+ profit_margin_percent: Some(10),
};
// summary:
diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs
index dfda40befa..84f872fdc0 100644
--- a/common/mixnet-contract/src/msg.rs
+++ b/common/mixnet-contract/src/msg.rs
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA
// SPDX-License-Identifier: Apache-2.0
+use crate::mixnode::NodeRewardParams;
use crate::StateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
@@ -36,6 +37,12 @@ pub enum ExecuteMsg {
// percentage value in range 0-100
uptime: u32,
},
+
+ RewardMixnodeV2 {
+ identity: IdentityKey,
+ // percentage value in range 0-100
+ params: NodeRewardParams,
+ },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -75,6 +82,10 @@ pub enum QueryMsg {
address: Addr,
},
LayerDistribution {},
+ GetRewardPool {},
+ GetCirculatingSupply {},
+ GetEpochRewardPercent {},
+ GetSybilResistancePercent {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs
index 1542c8a3ad..e02b6e738e 100644
--- a/common/network-defaults/src/lib.rs
+++ b/common/network-defaults/src/lib.rs
@@ -93,4 +93,11 @@ 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 = 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;
+
+pub const DEFAULT_PROFIT_MARGIN: u8 = 10;
diff --git a/contracts/mixnet/.gitignore b/contracts/mixnet/.gitignore
new file mode 100644
index 0000000000..1761c01d05
--- /dev/null
+++ b/contracts/mixnet/.gitignore
@@ -0,0 +1 @@
+.envrc
\ No newline at end of file
diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock
index 90c5f59a0f..0f7f85618a 100644
--- a/contracts/mixnet/Cargo.lock
+++ b/contracts/mixnet/Cargo.lock
@@ -23,9 +23,9 @@ dependencies = [
[[package]]
name = "ast_node"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93f52ce8fac3d0e6720a92b0576d737c01b1b5db4dd786e962e5925f00bf755"
+checksum = "e96d5444b02f3080edac8a144f6baf29b2fb6ff589ad4311559731a7c7529381"
dependencies = [
"darling",
"pmutil",
@@ -41,12 +41,24 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+[[package]]
+name = "az"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
+
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "block-buffer"
version = "0.7.3"
@@ -79,9 +91,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.7.1"
+version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
+checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
@@ -89,6 +101,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytemuck"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -121,9 +139,9 @@ dependencies = [
[[package]]
name = "const-oid"
-version = "0.6.0"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22"
+checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
[[package]]
name = "cosmwasm-crypto"
@@ -147,9 +165,9 @@ dependencies = [
[[package]]
name = "cosmwasm-schema"
-version = "0.14.0"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6a9a6a4ca3b4d7b56f943312a65857cdfc84424f9c2773889f4cd17f36fba61"
+checksum = "04159eec9b583671db7923ff2b979736dfb8f0152347cab9fd02373c22e1a870"
dependencies = [
"schemars",
"serde_json",
@@ -181,9 +199,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
-version = "0.1.4"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
+checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
@@ -196,9 +214,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-bigint"
-version = "0.2.2"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b32a398eb1ccfbe7e4f452bc749c44d38dd732e9a253f19da224c416f00ee7f4"
+checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
@@ -218,9 +236,9 @@ dependencies = [
[[package]]
name = "curve25519-dalek"
-version = "3.1.0"
+version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3"
+checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
"byteorder",
"digest 0.9.0",
@@ -266,9 +284,9 @@ dependencies = [
[[package]]
name = "der"
-version = "0.4.0"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f215f706081a44cb702c71c39a52c05da637822e9c1645a50b7202689e982d"
+checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
dependencies = [
"const-oid",
]
@@ -338,9 +356,9 @@ checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]]
name = "ecdsa"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "713c32426287891008edb98f8b5c6abb2130aa043c93a818728fcda78606f274"
+checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [
"der",
"elliptic-curve",
@@ -370,9 +388,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "elliptic-curve"
-version = "0.10.4"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83e5c176479da93a0983f0a6fdc3c1b8e7d5be0d7fe3fe05a99f15b96582b9a8"
+checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"crypto-bigint",
"ff",
@@ -404,14 +422,26 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "ff"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63eec06c61e487eecf0f7e6e6372e596a81922c28d33e645d6983ca6493a1af0"
+checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"rand_core 0.6.3",
"subtle",
]
+[[package]]
+name = "fixed"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
+dependencies = [
+ "az",
+ "bytemuck",
+ "half",
+ "typenum",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -501,6 +531,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
[[package]]
name = "handlebars"
version = "3.5.5"
@@ -564,6 +600,15 @@ dependencies = [
"unicode-normalization",
]
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
[[package]]
name = "is-macro"
version = "0.1.9"
@@ -579,15 +624,15 @@ dependencies = [
[[package]]
name = "itoa"
-version = "0.4.7"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "k256"
-version = "0.9.5"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "008b0281ca8032567c9711cd48631781c15228301860a39b32deb28d63125e46"
+checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if 1.0.0",
"ecdsa",
@@ -603,9 +648,18 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.100"
+version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
+checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
+
+[[package]]
+name = "lock_api"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+dependencies = [
+ "scopeguard",
+]
[[package]]
name = "log"
@@ -624,9 +678,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
@@ -638,10 +692,15 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
+ "az",
"cosmwasm-std",
+ "fixed",
+ "log",
+ "network-defaults",
"schemars",
"serde",
"serde_repr",
+ "thiserror",
"ts-rs",
]
@@ -653,6 +712,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
+ "fixed",
"mixnet-contract",
"schemars",
"serde",
@@ -732,6 +792,31 @@ dependencies = [
"stable_deref_trait",
]
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if 1.0.0",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -802,9 +887,9 @@ dependencies = [
[[package]]
name = "pkcs8"
-version = "0.7.5"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbee84ed13e44dd82689fa18348a49934fa79cc774a344c42fc9b301c71b140a"
+checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"spki",
@@ -823,9 +908,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
-version = "0.2.10"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "precomputed-hash"
@@ -835,9 +920,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
-version = "1.0.24"
+version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid",
]
@@ -850,9 +935,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
-version = "1.0.8"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
@@ -917,6 +1002,15 @@ dependencies = [
"rand_core 0.5.1",
]
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "regex"
version = "1.5.4"
@@ -942,9 +1036,9 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schemars"
-version = "0.8.3"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6ab463ae35acccb5cba66c0084c985257b797d288b6050cc2f6ac1b266cb78"
+checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409"
dependencies = [
"dyn-clone",
"schemars_derive",
@@ -954,9 +1048,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
-version = "0.8.3"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "902fdfbcf871ae8f653bddf4b2c05905ddaabc08f69d32a915787e3be0d31356"
+checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8"
dependencies = [
"proc-macro2",
"quote",
@@ -971,10 +1065,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
-name = "serde"
-version = "1.0.122"
+name = "scopeguard"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "974ef1bd2ad8a507599b336595454081ff68a9599b4890af7643c0c0ed73a62c"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
@@ -990,9 +1090,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.122"
+version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dee1f300f838c8ac340ecb0112b3ac472464fa67e87292bdb3dfc9c49128e17"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
@@ -1012,9 +1112,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.61"
+version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
+checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [
"itoa",
"ryu",
@@ -1046,9 +1146,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.9.5"
+version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
+checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
@@ -1059,9 +1159,9 @@ dependencies = [
[[package]]
name = "signature"
-version = "1.3.1"
+version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
+checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
dependencies = [
"digest 0.9.0",
"rand_core 0.6.3",
@@ -1075,15 +1175,15 @@ checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
[[package]]
name = "smallvec"
-version = "1.6.1"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "spki"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521"
+checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
]
@@ -1102,12 +1202,13 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
+checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6"
dependencies = [
"lazy_static",
"new_debug_unreachable",
+ "parking_lot",
"phf_shared",
"precomputed-hash",
"serde",
@@ -1146,15 +1247,15 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "subtle"
-version = "2.4.0"
+version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
-version = "0.2.7"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "837a3ef86c2817228e733b6f173c821fd76f9eb21a0bc9001a826be48b00b4e7"
+checksum = "9f5229fe227ff0060e13baa386d6e368797700eab909523f730008d191ee53ae"
dependencies = [
"string_cache",
"string_cache_codegen",
@@ -1254,9 +1355,9 @@ dependencies = [
[[package]]
name = "swc_macros_common"
-version = "0.3.3"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08ed2e930f5a1a4071fe62c90fd3a296f6030e5d94bfe13993244423caf59a78"
+checksum = "bf7c68e78ffbcba3d38abe6d0b76a0e1a37888b5c9301db3426537207090ada3"
dependencies = [
"pmutil",
"proc-macro2",
@@ -1266,9 +1367,9 @@ dependencies = [
[[package]]
name = "swc_visit"
-version = "0.2.6"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a423caa0b4585118164dbad8f1ad52b592a9a9370b25decc4d84c6b4309132c0"
+checksum = "f8511a4788ab29daf00bee23e425aac92c9be4eec74c98fec4a45d0e710be695"
dependencies = [
"either",
"swc_visit_macros",
@@ -1290,9 +1391,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.65"
+version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
+checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
dependencies = [
"proc-macro2",
"quote",
@@ -1301,18 +1402,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.23"
+version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.23"
+version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
@@ -1321,9 +1422,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.1"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a776787d9c5d455bec3db044586ccdd8a9c74d5da5dc319fb80f3db08808fe6"
+checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd"
dependencies = [
"libc",
"time-macros",
@@ -1331,15 +1432,15 @@ dependencies = [
[[package]]
name = "time-macros"
-version = "0.2.1"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04a153416002296880a3b51329a0e3df31c779c53ec827993e865ce427982843"
+checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
[[package]]
name = "tinyvec"
-version = "1.3.1"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
+checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
dependencies = [
"tinyvec_macros",
]
@@ -1383,9 +1484,9 @@ dependencies = [
[[package]]
name = "typenum"
-version = "1.13.0"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "ucd-trie"
@@ -1395,9 +1496,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
-version = "0.9.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e"
+checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
dependencies = [
"byteorder",
"crunchy",
@@ -1407,12 +1508,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.5"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
-dependencies = [
- "matches",
-]
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-normalization"
@@ -1431,9 +1529,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "url"
@@ -1466,7 +1564,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
-name = "zeroize"
-version = "1.3.0"
+name = "winapi"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zeroize"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml
index 13bd3ed584..0d803ecae1 100644
--- a/contracts/mixnet/Cargo.toml
+++ b/contracts/mixnet/Cargo.toml
@@ -49,3 +49,4 @@ thiserror = { version = "1.0.23" }
[dev-dependencies]
cosmwasm-schema = { version = "0.14.0" }
+fixed = "1.1"
diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs
index 4dc9dac8e4..eb360bd686 100644
--- a/contracts/mixnet/src/contract.rs
+++ b/contracts/mixnet/src/contract.rs
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA
// SPDX-License-Identifier: Apache-2.0
+use std::u128;
+
use crate::helpers::calculate_epoch_reward_rate;
use crate::state::State;
use crate::storage::{config, layer_distribution};
@@ -26,6 +28,13 @@ pub const INITIAL_MIXNODE_DELEGATION_REWARD_RATE: u64 = 110;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
+pub const INITIAL_REWARD_POOL: u128 = 250_000_000_000_000;
+pub const EPOCH_REWARD_PERCENT: u8 = 2; // Used to calculate epoch reward pool
+pub const DEFAULT_SYBIL_RESISTANCE_PERCENT: u8 = 30;
+
+// 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;
+
fn default_initial_state(owner: Addr) -> State {
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE);
@@ -94,6 +103,9 @@ pub fn execute(
ExecuteMsg::RewardMixnode { identity, uptime } => {
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
}
+ ExecuteMsg::RewardMixnodeV2 { identity, params } => {
+ transactions::try_reward_mixnode_v2(deps, env, info, identity, params)
+ }
ExecuteMsg::DelegateToMixnode { mix_identity } => {
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
@@ -151,11 +163,14 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_reward_pool(deps)),
+ QueryMsg::GetCirculatingSupply {} => to_binary(&queries::query_circulating_supply(deps)),
+ QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT),
+ QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT),
};
Ok(query_res?)
}
-
#[entry_point]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result {
Ok(Default::default())
diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs
index 3610edee30..6d3db45be5 100644
--- a/contracts/mixnet/src/error.rs
+++ b/contracts/mixnet/src/error.rs
@@ -77,4 +77,12 @@ pub enum ContractError {
identity: IdentityKey,
address: Addr,
},
+ #[error("Overflow error!")]
+ Overflow(#[from] cosmwasm_std::OverflowError),
+
+ #[error("We tried to remove more funds then are available in the Reward pool. Wanted to remove {to_remove}, but have only {reward_pool}")]
+ OutOfFunds { to_remove: u128, reward_pool: u128 },
+
+ #[error("Invalid ratio")]
+ Ratio(#[from] mixnet_contract::error::MixnetContractError),
}
diff --git a/contracts/mixnet/src/helpers.rs b/contracts/mixnet/src/helpers.rs
index de5636a0ac..6c86b4034e 100644
--- a/contracts/mixnet/src/helpers.rs
+++ b/contracts/mixnet/src/helpers.rs
@@ -19,11 +19,11 @@ const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000_000_000_000u128);
// cosmwasm bucket internal value
const NAMESPACE_LENGTH: usize = 2;
-fn decimal_to_uint128(value: Decimal) -> Uint128 {
+pub fn decimal_to_uint128(value: Decimal) -> Uint128 {
value * DECIMAL_FRACTIONAL
}
-fn uint128_to_decimal(value: Uint128) -> Decimal {
+pub fn uint128_to_decimal(value: Uint128) -> Decimal {
Decimal::from_ratio(value, DECIMAL_FRACTIONAL)
}
diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs
index e76d62a93c..7453b2e1f9 100644
--- a/contracts/mixnet/src/queries.rs
+++ b/contracts/mixnet/src/queries.rs
@@ -4,12 +4,12 @@
use crate::error::ContractError;
use crate::helpers::get_all_delegations_paged;
use crate::storage::{
- all_mix_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
- mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params,
- reverse_mix_delegations_read,
+ all_mix_delegations_read, circulating_supply, gateways_owners_read, gateways_read,
+ mix_delegations_read, mixnodes_owners_read, mixnodes_read, read_layer_distribution,
+ read_state_params, reverse_mix_delegations_read, reward_pool_value,
};
use config::defaults::DENOM;
-use cosmwasm_std::{coin, Addr, Deps, Order, StdResult};
+use cosmwasm_std::{coin, Addr, Deps, Order, StdResult, Uint128};
use mixnet_contract::{
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayResponse,
@@ -91,6 +91,14 @@ pub(crate) fn query_layer_distribution(deps: Deps) -> LayerDistribution {
read_layer_distribution(deps.storage)
}
+pub(crate) fn query_reward_pool(deps: Deps) -> Uint128 {
+ reward_pool_value(deps.storage)
+}
+
+pub(crate) fn query_circulating_supply(deps: Deps) -> Uint128 {
+ circulating_supply(deps.storage)
+}
+
/// Adds a 0 byte to terminate the `start_after` value given. This allows CosmWasm
/// to get the succeeding key as the start of the next page.
// S works for both `String` and `Addr` and that's what we wanted
diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs
index 80aaddecf3..c95e909a03 100644
--- a/contracts/mixnet/src/storage.rs
+++ b/contracts/mixnet/src/storage.rs
@@ -1,14 +1,16 @@
// Copyright 2021 - Nym Technologies SA
// SPDX-License-Identifier: Apache-2.0
-
-use crate::queries;
+use crate::contract::INITIAL_REWARD_POOL;
use crate::state::State;
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
+use crate::{error::ContractError, queries};
+use config::defaults::TOTAL_SUPPLY;
use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
+use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
RawDelegationData, StateParams,
@@ -24,9 +26,10 @@ use serde::Serialize;
// singletons
const CONFIG_KEY: &[u8] = b"config";
const LAYER_DISTRIBUTION_KEY: &[u8] = b"layers";
+const REWARD_POOL_PREFIX: &[u8] = b"pool";
// buckets
-const PREFIX_MIXNODES: &[u8] = b"mn";
+pub const PREFIX_MIXNODES: &[u8] = b"mn";
const PREFIX_MIXNODES_OWNERS: &[u8] = b"mo";
const PREFIX_GATEWAYS: &[u8] = b"gt";
const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
@@ -46,6 +49,53 @@ pub fn config_read(storage: &dyn Storage) -> ReadonlySingleton {
singleton_read(storage, CONFIG_KEY)
}
+fn reward_pool(storage: &dyn Storage) -> ReadonlySingleton {
+ singleton_read(storage, REWARD_POOL_PREFIX)
+}
+
+pub fn mut_reward_pool(storage: &mut dyn Storage) -> Singleton {
+ singleton(storage, REWARD_POOL_PREFIX)
+}
+
+pub fn reward_pool_value(storage: &dyn Storage) -> Uint128 {
+ match reward_pool(storage).load() {
+ Ok(value) => value,
+ Err(_e) => Uint128(INITIAL_REWARD_POOL),
+ }
+}
+
+#[allow(dead_code)]
+pub fn incr_reward_pool(
+ amount: Uint128,
+ storage: &mut dyn Storage,
+) -> Result {
+ let stake = reward_pool_value(storage).saturating_add(amount);
+ mut_reward_pool(storage).save(&stake)?;
+ Ok(stake)
+}
+
+pub fn decr_reward_pool(
+ amount: Uint128,
+ storage: &mut dyn Storage,
+) -> Result {
+ let stake = match reward_pool_value(storage).checked_sub(amount) {
+ Ok(stake) => stake,
+ Err(_e) => {
+ return Err(ContractError::OutOfFunds {
+ to_remove: amount.u128(),
+ reward_pool: reward_pool_value(storage).u128(),
+ })
+ }
+ };
+ mut_reward_pool(storage).save(&stake)?;
+ Ok(stake)
+}
+
+pub fn circulating_supply(storage: &dyn Storage) -> Uint128 {
+ let reward_pool = reward_pool_value(storage).u128();
+ Uint128(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,
@@ -196,6 +246,55 @@ pub(crate) fn increase_mix_delegated_stakes(
Ok(total_rewarded)
}
+pub(crate) fn increase_mix_delegated_stakes_v2(
+ storage: &mut dyn Storage,
+ bond: &MixNodeBond,
+ params: &NodeRewardParams,
+) -> Result {
+ let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
+
+ let mut total_rewarded = Uint128::zero();
+ let mut chunk_start: Option> = None;
+ loop {
+ // get `chunk_size` of delegations
+ let delegations_chunk = mix_delegations_read(storage, bond.identity())
+ .range(chunk_start.as_deref(), None, Order::Ascending)
+ .take(chunk_size)
+ .collect::>>()?;
+
+ if delegations_chunk.is_empty() {
+ break;
+ }
+
+ // append 0 byte to the last value to start with whatever is the next succeeding key
+ chunk_start = Some(
+ delegations_chunk
+ .last()
+ .unwrap()
+ .0
+ .iter()
+ .cloned()
+ .chain(std::iter::once(0u8))
+ .collect(),
+ );
+
+ // 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
+ <= params.reward_blockstamp()
+ {
+ let reward = bond.reward_delegation(delegation.amount, params);
+ delegation.amount += Uint128(reward);
+ total_rewarded += Uint128(reward);
+ mix_delegations(storage, bond.identity()).save(&delegator_address, &delegation)?;
+ }
+ }
+ }
+
+ Ok(total_rewarded)
+}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_mixnode_bond(
@@ -345,6 +444,7 @@ mod tests {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
+ profit_margin_percent: Some(10),
};
mixnodes(&mut storage)
diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs
index 70b761e0fd..0b36814fb7 100644
--- a/contracts/mixnet/src/support/tests.rs
+++ b/contracts/mixnet/src/support/tests.rs
@@ -133,6 +133,7 @@ pub mod helpers {
Layer::One,
12_345,
mix_node,
+ None,
)
}
diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs
index 8173243bbc..48254473a6 100644
--- a/contracts/mixnet/src/transactions.rs
+++ b/contracts/mixnet/src/transactions.rs
@@ -1,6 +1,5 @@
// Copyright 2021 - Nym Technologies SA
// SPDX-License-Identifier: Apache-2.0
-
use crate::error::ContractError;
use crate::helpers::{calculate_epoch_reward_rate, scale_reward_by_uptime, Delegations};
use crate::queries;
@@ -10,6 +9,7 @@ use cosmwasm_std::{
attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
};
use cosmwasm_storage::ReadonlyBucket;
+use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{
Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams,
};
@@ -99,6 +99,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
@@ -416,6 +417,65 @@ pub(crate) fn try_reward_mixnode(
})
}
+pub(crate) fn try_reward_mixnode_v2(
+ deps: DepsMut,
+ env: Env,
+ info: MessageInfo,
+ mix_identity: IdentityKey,
+ params: NodeRewardParams,
+) -> Result {
+ let state = config_read(deps.storage).load().unwrap();
+
+ // check if this is executed by the monitor, if not reject the transaction
+ if info.sender != state.network_monitor_address {
+ return Err(ContractError::Unauthorized);
+ }
+
+ // check if the bond even exists
+ let mut current_bond = match mixnodes_read(deps.storage).load(mix_identity.as_bytes()) {
+ Ok(bond) => bond,
+ Err(_) => {
+ return Ok(Response {
+ attributes: vec![attr("result", "bond not found")],
+ ..Default::default()
+ });
+ }
+ };
+
+ let mut reward_params = params;
+
+ reward_params.set_reward_blockstamp(env.block.height);
+
+ let reward_result = current_bond.reward(&reward_params);
+
+ // Omitting the price per packet function now, it follows that base operator reward is the node_reward
+ let operator_reward = current_bond.operator_reward(&reward_params);
+
+ let total_delegation_reward =
+ increase_mix_delegated_stakes_v2(deps.storage, ¤t_bond, &reward_params)?;
+
+ // 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
+ <= reward_params.reward_blockstamp()
+ {
+ current_bond.bond_amount.amount += Uint128(operator_reward);
+ current_bond.total_delegation.amount += total_delegation_reward;
+ mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?;
+ decr_reward_pool(Uint128(operator_reward), deps.storage)?;
+ decr_reward_pool(total_delegation_reward, deps.storage)?;
+ }
+
+ Ok(Response {
+ submessages: vec![],
+ messages: vec![],
+ attributes: vec![
+ attr("bond increase", reward_result.reward()),
+ attr("total delegation increase", total_delegation_reward),
+ ],
+ data: None,
+ })
+}
fn validate_delegation_stake(delegation: &[Coin]) -> Result<(), ContractError> {
// check if anything was put as delegation
if delegation.is_empty() {
@@ -459,8 +519,10 @@ pub(crate) fn try_delegate_to_mixnode(
}
};
+ let amount = info.funds[0].amount;
+
// update total_delegation of this node
- current_bond.total_delegation.amount += info.funds[0].amount;
+ current_bond.total_delegation.amount += amount;
mixnodes_bucket.save(mix_identity.as_bytes(), ¤t_bond)?;
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
@@ -468,8 +530,8 @@ pub(crate) fn try_delegate_to_mixnode(
// write the delegation
let new_amount = match delegation_bucket.may_load(sender_bytes)? {
- Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount,
- None => info.funds[0].amount,
+ Some(existing_delegation) => existing_delegation.amount + amount,
+ None => amount,
};
// the block height is reset, if it existed
let new_delegation = RawDelegationData::new(new_amount, env.block.height);
@@ -532,8 +594,9 @@ pub(crate) fn try_remove_delegation_from_mixnode(
pub mod tests {
use super::*;
use crate::contract::{
- execute, query, INITIAL_DEFAULT_EPOCH_LENGTH, INITIAL_GATEWAY_BOND, INITIAL_MIXNODE_BOND,
- INITIAL_MIXNODE_BOND_REWARD_RATE, INITIAL_MIXNODE_DELEGATION_REWARD_RATE,
+ execute, query, DEFAULT_SYBIL_RESISTANCE_PERCENT, INITIAL_DEFAULT_EPOCH_LENGTH,
+ INITIAL_GATEWAY_BOND, INITIAL_MIXNODE_BOND, INITIAL_MIXNODE_BOND_REWARD_RATE,
+ INITIAL_MIXNODE_DELEGATION_REWARD_RATE,
};
use crate::helpers::calculate_epoch_reward_rate;
use crate::queries::DELEGATION_PAGE_DEFAULT_LIMIT;
@@ -1506,6 +1569,7 @@ pub mod tests {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
+ profit_margin_percent: Some(10),
};
mixnodes(deps.as_mut().storage)
@@ -1605,6 +1669,7 @@ pub mod tests {
identity_key: node_identity.clone(),
..mix_node_fixture()
},
+ profit_margin_percent: Some(10),
};
mixnodes(deps.as_mut().storage)
@@ -2654,4 +2719,128 @@ pub mod tests {
assert_eq!(bob_node.mix_node.identity_key, "bob");
assert_eq!(bob_node.layer, Layer::Two);
}
+
+ #[test]
+ fn test_tokenomics_rewarding() {
+ use crate::contract::{EPOCH_REWARD_PERCENT, INITIAL_REWARD_POOL};
+
+ type U128 = fixed::types::U75F53;
+
+ let mut deps = helpers::init_contract();
+ 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 period_reward_pool = (INITIAL_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).u128();
+ 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(),
+ mock_env(),
+ mock_info(
+ "alice",
+ &vec![Coin {
+ denom: DENOM.to_string(),
+ amount: Uint128(10000_000_000),
+ }],
+ ),
+ MixNode {
+ identity_key: "alice".to_string(),
+ ..helpers::mix_node_fixture()
+ },
+ )
+ .unwrap();
+
+ try_delegate_to_mixnode(
+ deps.as_mut(),
+ mock_env(),
+ mock_info("d1", &vec![coin(8000_000000, DENOM)]),
+ "alice".to_string(),
+ )
+ .unwrap();
+
+ try_delegate_to_mixnode(
+ deps.as_mut(),
+ mock_env(),
+ mock_info("d2", &vec![coin(2000_000000, DENOM)]),
+ "alice".to_string(),
+ )
+ .unwrap();
+
+ 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"alice").unwrap();
+ let mix_1_uptime = 100;
+
+ let mut params = NodeRewardParams::new(
+ period_reward_pool,
+ k,
+ 0,
+ circulating_supply,
+ mix_1_uptime,
+ DEFAULT_SYBIL_RESISTANCE_PERCENT,
+ );
+
+ params.set_reward_blockstamp(env.block.height);
+
+ assert_eq!(params.performance(), 1);
+
+ let mix_1_reward_result = mix_1.reward(¶ms);
+
+ assert_eq!(
+ mix_1_reward_result.sigma(),
+ U128::from_num(0.0000266666666666)
+ );
+ assert_eq!(
+ mix_1_reward_result.lambda(),
+ U128::from_num(0.0000133333333333)
+ );
+ assert_eq!(mix_1_reward_result.reward().int(), 102646153);
+
+ let mix1_operator_profit = mix_1.operator_reward(¶ms);
+
+ let mix1_delegator1_reward = mix_1.reward_delegation(Uint128(8000_000000), ¶ms);
+
+ let mix1_delegator2_reward = mix_1.reward_delegation(Uint128(2000_000000), ¶ms);
+
+ assert_eq!(mix1_operator_profit, U128::from_num(74455384));
+ assert_eq!(mix1_delegator1_reward, U128::from_num(22552615));
+ assert_eq!(mix1_delegator2_reward, U128::from_num(5638153));
+
+ let pre_reward_bond = read_mixnode_bond(&deps.storage, b"alice").unwrap().u128();
+ assert_eq!(pre_reward_bond, 10000_000_000);
+
+ let pre_reward_delegation = read_mixnode_delegation(&deps.storage, b"alice")
+ .unwrap()
+ .u128();
+ assert_eq!(pre_reward_delegation, 10000_000_000);
+
+ try_reward_mixnode_v2(deps.as_mut(), env, info, "alice".to_string(), params).unwrap();
+
+ assert_eq!(
+ read_mixnode_bond(&deps.storage, b"alice").unwrap().u128(),
+ U128::from_num(pre_reward_bond) + U128::from_num(mix1_operator_profit)
+ );
+ assert_eq!(
+ read_mixnode_delegation(&deps.storage, b"alice")
+ .unwrap()
+ .u128(),
+ pre_reward_delegation + mix1_delegator1_reward + mix1_delegator2_reward
+ );
+
+ assert_eq!(
+ reward_pool_value(&deps.storage).u128(),
+ U128::from_num(INITIAL_REWARD_POOL)
+ - (U128::from_num(mix1_operator_profit)
+ + U128::from_num(mix1_delegator1_reward)
+ + U128::from_num(mix1_delegator2_reward))
+ )
+ }
}
diff --git a/tauri-wallet/Cargo.lock b/tauri-wallet/Cargo.lock
index 869006a638..af3408d876 100644
--- a/tauri-wallet/Cargo.lock
+++ b/tauri-wallet/Cargo.lock
@@ -130,6 +130,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+[[package]]
+name = "az"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
+
[[package]]
name = "base64"
version = "0.13.0"
@@ -288,6 +294,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+[[package]]
+name = "bytemuck"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
+
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -1197,6 +1209,18 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "fixed"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
+dependencies = [
+ "az",
+ "bytemuck",
+ "half",
+ "typenum",
+]
+
[[package]]
name = "flate2"
version = "1.0.21"
@@ -1741,6 +1765,12 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
[[package]]
name = "handlebars"
version = "3.5.5"
@@ -2329,10 +2359,15 @@ dependencies = [
name = "mixnet-contract"
version = "0.1.0"
dependencies = [
+ "az",
"cosmwasm-std",
+ "fixed",
+ "log",
+ "network-defaults",
"schemars",
"serde",
"serde_repr",
+ "thiserror",
"ts-rs",
]
diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml
index 089c8d2529..72150c1c6e 100644
--- a/validator-api/Cargo.toml
+++ b/validator-api/Cargo.toml
@@ -40,6 +40,7 @@ getset = "0.1.1"
rocket_sync_db_pools = {version = "0.1.0-rc.1", default-features = false}
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]}
+
## internal
config = { path = "../common/config" }
crypto = { path="../common/crypto" }
@@ -54,6 +55,8 @@ credentials = { path = "../common/credentials", optional = true }
[features]
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
+default = ["tokenomics"]
+tokenomics = []
[build-dependencies]
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs
index 7a1cf9864b..33de1177c2 100644
--- a/validator-api/src/cache/mod.rs
+++ b/validator-api/src/cache/mod.rs
@@ -75,7 +75,7 @@ impl ValidatorCacheRefresher {
{
let (mixnodes, gateways) = tokio::try_join!(
self.nymd_client.get_mixnodes(),
- self.nymd_client.get_gateways()
+ self.nymd_client.get_gateways(),
)?;
let state_params = self.nymd_client.get_state_params().await?;
@@ -83,7 +83,7 @@ impl ValidatorCacheRefresher {
info!(
"Updating validator cache. There are {} mixnodes and {} gateways",
mixnodes.len(),
- gateways.len()
+ gateways.len(),
);
self.cache
diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs
index 38dbd1e488..f4f73a41e4 100644
--- a/validator-api/src/nymd_client.rs
+++ b/validator-api/src/nymd_client.rs
@@ -93,6 +93,34 @@ impl Client {
Ok(time)
}
+ pub(crate) async fn get_reward_pool(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.0.read().await.get_reward_pool().await?)
+ }
+
+ pub(crate) async fn get_circulating_supply(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.0.read().await.get_circulating_supply().await?)
+ }
+
+ pub(crate) async fn get_sybil_resistance_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.0.read().await.get_sybil_resistance_percent().await?)
+ }
+
+ pub(crate) async fn get_epoch_reward_percent(&self) -> Result
+ where
+ C: CosmWasmClient + Sync,
+ {
+ Ok(self.0.read().await.get_epoch_reward_percent().await?)
+ }
+
pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError>
where
C: CosmWasmClient + Sync,
diff --git a/validator-api/src/rewarding/mod.rs b/validator-api/src/rewarding/mod.rs
index e1bac6fe73..94dfc7ef3f 100644
--- a/validator-api/src/rewarding/mod.rs
+++ b/validator-api/src/rewarding/mod.rs
@@ -12,6 +12,7 @@ use crate::storage::models::{
};
use crate::storage::ValidatorApiStorage;
use log::{error, info};
+use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{ExecuteMsg, IdentityKey};
use std::collections::HashMap;
use std::convert::TryInto;
@@ -48,6 +49,19 @@ pub(crate) struct MixnodeToReward {
/// Total number of individual addresses that have delegated to this particular node
pub(crate) total_delegations: usize,
+ /// Node absolute uptime over total active set uptime
+ params: Option,
+}
+
+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 params(&self) -> Option {
+ if cfg!(feature = "tokenomics") {
+ self.params
+ } else {
+ None
+ }
+ }
}
pub(crate) struct FailedMixnodeRewardChunkDetails {
@@ -62,9 +76,16 @@ pub(crate) struct FailureData {
impl<'a> From<&'a MixnodeToReward> for ExecuteMsg {
fn from(node: &MixnodeToReward) -> Self {
- ExecuteMsg::RewardMixnode {
- identity: node.identity.clone(),
- uptime: node.uptime.u8() as u32,
+ if let Some(params) = node.params() {
+ ExecuteMsg::RewardMixnodeV2 {
+ identity: node.identity.clone(),
+ params,
+ }
+ } else {
+ ExecuteMsg::RewardMixnode {
+ identity: node.identity.clone(),
+ uptime: node.uptime.u8() as u32,
+ }
}
}
}
@@ -185,12 +206,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 state = self.nymd_client.get_state_params().await?;
// 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)
// 3. determine uptime and attach delegators count
- let eligible_nodes = active_mixnodes
+ let mut eligible_nodes: Vec = active_mixnodes
.iter()
.filter_map(|mix| {
mixnode_delegators
@@ -199,11 +221,39 @@ impl Rewarder {
identity: mix.identity.clone(),
uptime: mix.last_day,
total_delegations,
+ params: None,
})
})
.filter(|node| node.uptime.u8() > 0)
.collect();
+ if cfg!(feature = "tokenomics") {
+ let reward_pool = self.nymd_client.get_reward_pool().await?;
+ let circulating_supply = self.nymd_client.get_circulating_supply().await?;
+ let sybil_resistance_percent = self.nymd_client.get_sybil_resistance_percent().await?;
+ let epoch_reward_percent = self.nymd_client.get_epoch_reward_percent().await?;
+ let k = state.mixnode_active_set_size;
+ let period_reward_pool = (reward_pool / 100) * epoch_reward_percent as u128;
+
+ info!("Rewarding pool stats");
+ info!("-- Reward pool: {} unym", reward_pool);
+ info!("---- Epoch reward pool: {} unym", period_reward_pool);
+ info!("-- Circulating supply: {} unym", circulating_supply);
+
+ for mix in eligible_nodes.iter_mut() {
+ mix.params = Some(NodeRewardParams::new(
+ period_reward_pool,
+ k.into(),
+ 0,
+ circulating_supply,
+ mix.uptime.u8().into(),
+ sybil_resistance_percent,
+ ));
+ }
+ } else {
+ info!("Tokenomics feature is OFF");
+ }
+
Ok(eligible_nodes)
}