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:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, }
|
||||
@@ -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())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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 }
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user