Introduce staking supply, contract and wallet (#1324)

* Introduce staking supply, contract and wallet

* Migration staking supply (#1327)

* Staking supply to params migration

* Include into migration entrypoint

* StateParams merge

* Add changelog

* Make everyone happy
This commit is contained in:
Drazen Urch
2022-06-10 12:36:35 +02:00
committed by GitHub
parent 68889bd362
commit eff732aa2c
18 changed files with 137 additions and 37 deletions
+4
View File
@@ -11,6 +11,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261])
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contrat: Added staking_supply field to ContractStateParams.
- rewarding: replace circulating supply with staking supply in reward calculations ([#1324])
- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284])
- validator-api: add detailed mixnode bond endpoints, and explorer-api makes use of that data to append stake saturation.
- validator-api: add Swagger to document the REST API ([#1249]).
@@ -24,6 +26,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- wallet: new delegation and rewards UI
- wallet: show version in nav bar
- wallet: contract admin route put back
- waller: staking_supply field to StateParams
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
### Fixed
@@ -61,6 +64,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1302]: https://github.com/nymtech/nym/pull/1302
[#1318]: https://github.com/nymtech/nym/pull/1318
[#1322]: https://github.com/nymtech/nym/pull/1322
[#1324]: https://github.com/nymtech/nym/pull/1324
[#1328]: https://github.com/nymtech/nym/pull/1328
[#1329]: https://github.com/nymtech/nym/pull/1329
@@ -220,9 +220,9 @@ impl DelegatorRewardParams {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
let staking_supply = U128::from_num(self.reward_params.staking_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let scaled_delegation_amount = delegation_amount / staking_supply;
// Div by zero checked above
let delegator_reward =
@@ -392,22 +392,21 @@ impl MixNodeBond {
self.total_delegation.clone()
}
pub fn stake_saturation(&self, circulating_supply: u128, rewarded_set_size: u32) -> U128 {
self.total_bond_to_circulating_supply(circulating_supply)
* U128::from_num(rewarded_set_size)
pub fn stake_saturation(&self, staking_supply: u128, rewarded_set_size: u32) -> U128 {
self.total_bond_to_staking_supply(staking_supply) * U128::from_num(rewarded_set_size)
}
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
// behave similarly to delegations.
// The question is should this be taken into account when calculating operator rewards?
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
pub fn pledge_to_staking_supply(&self, staking_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(staking_supply)
}
pub fn total_bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
pub fn total_bond_to_staking_supply(&self, staking_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
/ U128::from_num(staking_supply)
}
pub fn lambda_ticked(&self, params: &RewardParams) -> U128 {
@@ -417,7 +416,7 @@ impl MixNodeBond {
pub fn lambda(&self, params: &RewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
self.pledge_to_circulating_supply(params.circulating_supply())
self.pledge_to_staking_supply(params.staking_supply())
}
pub fn sigma_ticked(&self, params: &RewardParams) -> U128 {
@@ -427,7 +426,7 @@ impl MixNodeBond {
pub fn sigma(&self, params: &RewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
self.total_bond_to_circulating_supply(params.circulating_supply())
self.total_bond_to_staking_supply(params.staking_supply())
}
pub fn estimate_reward(
@@ -515,9 +514,8 @@ impl MixNodeBond {
}
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_bond_to_circulating_supply(params.circulating_supply())
if self.total_bond_to_staking_supply(params.staking_supply()) < params.one_over_k() {
self.total_bond_to_staking_supply(params.staking_supply())
} else {
params.one_over_k()
}
@@ -78,9 +78,9 @@ impl NodeEpochRewards {
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
let staking_supply = U128::from_num(epoch_reward_params.staking_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let scaled_delegation_amount = delegation_amount / staking_supply;
let check_div_by_zero =
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
@@ -105,7 +105,8 @@ pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
circulating_supply: Uint128,
#[serde(alias = "circulating_supply")]
staking_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
}
@@ -115,7 +116,7 @@ impl EpochRewardParams {
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
circulating_supply: u128,
staking_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
@@ -123,7 +124,7 @@ impl EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
circulating_supply: Uint128::new(circulating_supply),
staking_supply: Uint128::new(staking_supply),
sybil_resistance_percent,
active_set_work_factor,
}
@@ -136,7 +137,7 @@ impl EpochRewardParams {
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
circulating_supply: Uint128::new(0),
staking_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
@@ -152,8 +153,8 @@ impl EpochRewardParams {
self.active_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
pub fn staking_supply(&self) -> u128 {
self.staking_supply.u128()
}
pub fn epoch_reward_pool(&self) -> u128 {
@@ -252,8 +253,8 @@ impl RewardParams {
self.epoch.rewarded_set_size.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.epoch.circulating_supply.u128()
pub fn staking_supply(&self) -> u128 {
self.epoch.staking_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
@@ -43,6 +43,7 @@ pub struct ContractStateParams {
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub mixnode_active_set_size: u32,
pub staking_supply: Uint128,
}
impl Display for ContractStateParams {
+50 -2
View File
@@ -57,6 +57,8 @@ pub const INITIAL_ACTIVE_SET_WORK_FACTOR: u8 = 10;
pub const DEFAULT_FIRST_INTERVAL_START: OffsetDateTime =
time::macros::datetime!(2022-01-01 12:00 UTC);
pub const INITIAL_STAKING_SUPPLY: Uint128 = Uint128::new(100_000_000_000_000);
pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
}
@@ -70,6 +72,7 @@ fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> Cont
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: INITIAL_MIXNODE_REWARDED_SET_SIZE,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
staking_supply: INITIAL_STAKING_SUPPLY,
},
}
}
@@ -432,7 +435,52 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
Ok(query_res?)
}
fn deal_with_zero_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
fn migrate_contract_state_params(deps: DepsMut<'_>) -> Result<(), ContractError> {
use crate::mixnet_contract_settings::storage::CONTRACT_STATE;
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct OldContractState {
pub owner: Addr,
pub rewarding_validator_address: Addr,
pub params: OldContractStateParams,
}
#[derive(Serialize, Deserialize)]
struct OldContractStateParams {
pub minimum_mixnode_pledge: Uint128,
pub minimum_gateway_pledge: Uint128,
pub mixnode_rewarded_set_size: u32,
pub mixnode_active_set_size: u32,
}
const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config");
let old_contract_state = OLD_CONTRACT_STATE.load(deps.storage)?;
let old_params = old_contract_state.params;
let new_params = ContractStateParams {
minimum_mixnode_pledge: old_params.minimum_mixnode_pledge,
minimum_gateway_pledge: old_params.minimum_gateway_pledge,
mixnode_rewarded_set_size: old_params.mixnode_rewarded_set_size,
mixnode_active_set_size: old_params.mixnode_active_set_size,
staking_supply: INITIAL_STAKING_SUPPLY,
};
let new_contract_state = ContractState {
owner: old_contract_state.owner,
rewarding_validator_address: old_contract_state.rewarding_validator_address,
params: new_params,
};
CONTRACT_STATE.save(deps.storage, &new_contract_state)?;
Ok(())
}
fn _deal_with_zero_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
// if there exists any delegation of 0 value, remove it
let zero_delegations = delegations()
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
@@ -466,7 +514,7 @@ fn deal_with_zero_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
deal_with_zero_delegations(deps)?;
migrate_contract_state_params(deps)?;
Ok(Default::default())
}
@@ -5,7 +5,7 @@ use super::storage;
use cosmwasm_std::{Deps, StdResult};
use mixnet_contract_common::{ContractStateParams, MixnetContractVersion};
pub(crate) fn query_contract_settings_params(deps: Deps<'_>) -> StdResult<ContractStateParams> {
pub fn query_contract_settings_params(deps: Deps<'_>) -> StdResult<ContractStateParams> {
storage::CONTRACT_STATE
.load(deps.storage)
.map(|settings| settings.params)
@@ -51,6 +51,7 @@ pub(crate) mod tests {
minimum_gateway_pledge: 456u128.into(),
mixnode_rewarded_set_size: 1000,
mixnode_active_set_size: 500,
staking_supply: 1000000u128.into(),
},
};
@@ -64,7 +64,7 @@ pub(crate) fn try_update_contract_settings(
#[cfg(test)]
pub mod tests {
use super::*;
use crate::contract::{INITIAL_GATEWAY_PLEDGE, INITIAL_MIXNODE_PLEDGE};
use crate::contract::{INITIAL_GATEWAY_PLEDGE, INITIAL_MIXNODE_PLEDGE, INITIAL_STAKING_SUPPLY};
use crate::error::ContractError;
use crate::mixnet_contract_settings::queries::query_rewarding_validator_address;
use crate::mixnet_contract_settings::transactions::try_update_contract_settings;
@@ -114,6 +114,7 @@ pub mod tests {
minimum_gateway_pledge: INITIAL_GATEWAY_PLEDGE,
mixnode_rewarded_set_size: 100,
mixnode_active_set_size: 50,
staking_supply: INITIAL_STAKING_SUPPLY,
};
let initial_params = storage::CONTRACT_STATE
+15 -4
View File
@@ -42,10 +42,21 @@ pub fn incr_reward_pool(
})
}
pub fn decr_reward_pool(
storage: &mut dyn Storage,
amount: Uint128,
) -> Result<Uint128, ContractError> {
pub fn reward_accounting(storage: &mut dyn Storage, amount: Uint128) -> Result<(), ContractError> {
decr_reward_pool(storage, amount)?;
incr_staking_supply(storage, amount)?;
Ok(())
}
fn incr_staking_supply(storage: &mut dyn Storage, amount: Uint128) -> Result<(), ContractError> {
let mut contract_state =
crate::mixnet_contract_settings::storage::CONTRACT_STATE.load(storage)?;
contract_state.params.staking_supply += amount;
crate::mixnet_contract_settings::storage::CONTRACT_STATE.save(storage, &contract_state)?;
Ok(())
}
fn decr_reward_pool(storage: &mut dyn Storage, amount: Uint128) -> Result<Uint128, ContractError> {
REWARD_POOL.update(storage, |current_pool| {
let stake = current_pool
.checked_sub(amount)
+1 -1
View File
@@ -678,7 +678,7 @@ pub(crate) fn try_reward_mixnode(
)?;
// Take rewards out of the rewarding pool
storage::decr_reward_pool(deps.storage, stored_node_result.reward())?;
storage::reward_accounting(deps.storage, stored_node_result.reward())?;
let rewarding_result = RewardingResult {
node_reward: stored_node_result.reward(),
@@ -0,0 +1,2 @@
export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, }
@@ -0,0 +1,2 @@
export interface ValidatorUrl { url: string, name: string | null, }
@@ -0,0 +1,2 @@
export interface Validator { nymd_url: string, nymd_name: string | null, api_url: string | null, }
+3
View File
@@ -17,6 +17,7 @@ pub struct TauriContractStateParams {
minimum_gateway_pledge: String,
mixnode_rewarded_set_size: u32,
mixnode_active_set_size: u32,
staking_supply: String,
}
impl From<ContractStateParams> for TauriContractStateParams {
@@ -26,6 +27,7 @@ impl From<ContractStateParams> for TauriContractStateParams {
minimum_gateway_pledge: p.minimum_gateway_pledge.to_string(),
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
mixnode_active_set_size: p.mixnode_active_set_size,
staking_supply: p.staking_supply.to_string(),
}
}
}
@@ -39,6 +41,7 @@ impl TryFrom<TauriContractStateParams> for ContractStateParams {
minimum_gateway_pledge: Uint128::try_from(p.minimum_gateway_pledge.as_str())?,
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
mixnode_active_set_size: p.mixnode_active_set_size,
staking_supply: Uint128::try_from(p.staking_supply.as_str())?,
})
}
}
+26
View File
@@ -63,6 +63,32 @@ const AdminForm: React.FC<{
helperText={errors?.mixnode_active_set_size?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_rewarded_set_size', { valueAsNumber: true })}
required
variant="outlined"
id="mixnode_rewarded_set_size"
name="mixnode_rewarded_set_size"
label="Mixnode Rewarded Set Size"
fullWidth
error={!!errors.mixnode_rewarded_set_size}
helperText={errors?.mixnode_rewarded_set_size?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('staking_supply', { valueAsNumber: true })}
required
variant="outlined"
id="staking_supply"
name="staking_supply"
label="Staking Supply"
fullWidth
error={!!errors.mixnode_rewarded_set_size}
helperText={errors?.mixnode_rewarded_set_size?.message}
/>
</Grid>
</Grid>
</Box>
<Grid
+1 -1
View File
@@ -1,2 +1,2 @@
export interface TauriContractStateParams { minimum_mixnode_pledge: string, minimum_gateway_pledge: string, mixnode_rewarded_set_size: number, mixnode_active_set_size: number, }
export interface TauriContractStateParams { minimum_mixnode_pledge: string, minimum_gateway_pledge: string, mixnode_rewarded_set_size: number, mixnode_active_set_size: number, stakin_supply: string }
+1 -1
View File
@@ -108,7 +108,7 @@ impl<C> ValidatorCacheRefresher<C> {
.map(|mixnode_bond| {
let stake_saturation = mixnode_bond
.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.staking_supply(),
interval_reward_params.rewarded_set_size() as u32,
)
.to_num();
+1 -1
View File
@@ -188,7 +188,7 @@ pub(crate) async fn get_mixnode_stake_saturation(
let interval_reward_params = interval_reward_params.into_inner();
let saturation = bond.mixnode_bond.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.staking_supply(),
interval_reward_params.rewarded_set_size() as u32,
);
+1 -1
View File
@@ -193,7 +193,7 @@ impl<C> Client<C> {
* interval_reward_percent as u128,
state.mixnode_rewarded_set_size as u128,
state.mixnode_active_set_size as u128,
this.get_circulating_supply().await?,
state.staking_supply.u128(),
this.get_sybil_resistance_percent().await?,
this.get_active_set_work_factor().await?,
);