From 189b83e7691f1d783967dc325e11dc5f3fd48d9e Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 31 May 2022 11:03:28 +0200 Subject: [PATCH] Fix uptime integer division, change lambda and sigma from ticked to regular (#1284) * Add more data to reward-estimate response * Fix uptime integer division error * typo * Reify tuple response * Fix uptime calculation * Use lambda and sigma instead of ticked versions for delegator and operator rewards calculation * Changelog --- CHANGELOG.md | 5 ++ .../mixnet-contract/src/mixnode.rs | 81 ++++++++++++------- .../mixnet-contract/src/reward_params.rs | 18 +++-- contracts/mixnet/src/rewards/transactions.rs | 21 +++-- explorer-api/src/mix_node/econ_stats.rs | 2 +- validator-api/src/node_status_api/routes.rs | 14 ++-- .../validator-api-requests/src/models.rs | 2 + 7 files changed, 90 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e395038495..8d8b2387a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,15 @@ - wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint. - mixnet-contract: Replace all naked `-` with `saturating_sub`. - validator-api: add Swagger to document the REST API ([#1249]). +- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284]) - all: added network compilation target to `--help` (or `--version`) commands ([#1256]). - network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]). ### Fixed +- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284]) +- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284]) +- mixnet-contract: `estimated_delegator_reward` calculation ([#1284]) - vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275]) - mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257]) - mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258]) @@ -29,6 +33,7 @@ [#1267]: https://github.com/nymtech/nym/pull/1267 [#1275]: https://github.com/nymtech/nym/pull/1275 [#1278]: https://github.com/nymtech/nym/pull/1278 +[#1284]: https://github.com/nymtech/nym/pull/1284 ## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 7447dde7bd..4325727195 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -318,6 +318,14 @@ impl NodeRewardResult { } } +pub struct RewardEstimate { + pub total_node_reward: u64, + pub operator_reward: u64, + pub delegators_reward: u64, + pub node_profit: u64, + pub operator_cost: u64, +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeBond { pub pledge_amount: Coin, @@ -410,63 +418,80 @@ impl MixNodeBond { / U128::from_num(circulating_supply) } + pub fn lambda_ticked(&self, params: &RewardParams) -> U128 { + // Ratio of a bond to the token circulating supply + self.lambda(params).min(params.one_over_k()) + } + pub fn lambda(&self, params: &RewardParams) -> U128 { // Ratio of a bond to the token circulating supply - let pledge_to_circulating_supply_ratio = - self.pledge_to_circulating_supply(params.circulating_supply()); - pledge_to_circulating_supply_ratio.min(params.one_over_k()) + self.pledge_to_circulating_supply(params.circulating_supply()) + } + + pub fn sigma_ticked(&self, params: &RewardParams) -> U128 { + // Ratio of a delegation to the the token circulating supply + self.sigma(params).min(params.one_over_k()) } pub fn sigma(&self, params: &RewardParams) -> U128 { // Ratio of a delegation to the the token circulating supply - let total_bond_to_circulating_supply_ratio = - self.total_bond_to_circulating_supply(params.circulating_supply()); - total_bond_to_circulating_supply_ratio.min(params.one_over_k()) + self.total_bond_to_circulating_supply(params.circulating_supply()) } pub fn estimate_reward( &self, params: &RewardParams, - ) -> Result<(u64, u64, u64), MixnetContractError> { + ) -> Result { let total_node_reward = self .reward(params) .reward() .checked_to_num::() .unwrap_or_default(); + let node_profit = self + .node_profit(params) + .checked_to_num::() + .unwrap_or_default(); + let operator_cost = params + .node + .operator_cost() + .checked_to_num::() + .unwrap_or_default(); let operator_reward = self.operator_reward(params); // Total reward has to be the sum of operator and delegator rewards - let delegators_reward = total_node_reward - operator_reward; + let delegators_reward = node_profit - operator_reward; - Ok(( - total_node_reward.try_into()?, - operator_reward.try_into()?, - delegators_reward.try_into()?, - )) + Ok(RewardEstimate { + total_node_reward: total_node_reward.try_into()?, + operator_reward: operator_reward.try_into()?, + delegators_reward: delegators_reward.try_into()?, + node_profit: node_profit.try_into()?, + operator_cost: operator_cost.try_into()?, + }) } + // keybase://chat/nymtech#dev-core/14473 pub fn reward(&self, params: &RewardParams) -> NodeRewardResult { - let lambda = self.lambda(params); - let sigma = self.sigma(params); + let lambda_ticked = self.lambda_ticked(params); + let sigma_ticked = self.sigma_ticked(params); let reward = params.performance() * params.epoch_reward_pool() - * (sigma * params.omega() - + params.alpha() * lambda * sigma * params.rewarded_set_size()) + * (sigma_ticked * params.omega() + + params.alpha() * lambda_ticked * sigma_ticked * params.rewarded_set_size()) / (ONE + params.alpha()); + // we only need regular lambda and sigma to calculate operator and delegator rewards NodeRewardResult { reward, - lambda, - sigma, + lambda: self.lambda(params), + sigma: self.sigma(params), } } pub fn node_profit(&self, params: &RewardParams) -> U128 { - if self.reward(params).reward() < params.node.operator_cost() { - U128::from_num(0u128) - } else { - self.reward(params).reward() - params.node.operator_cost() - } + self.reward(params) + .reward() + .saturating_sub(params.node.operator_cost()) } pub fn operator_reward(&self, params: &RewardParams) -> u128 { @@ -474,11 +499,9 @@ impl MixNodeBond { if reward.sigma == 0 { return 0; } - let profit = if reward.reward < params.node.operator_cost() { - U128::from_num(0u128) - } else { - reward.reward - params.node.operator_cost() - }; + + let profit = reward.reward.saturating_sub(params.node.operator_cost()); + let operator_base_reward = reward.reward.min(params.node.operator_cost()); // Div by zero checked above let operator_reward = (self.profit_margin() diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index ec911e7e1d..b033e06c60 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -42,7 +42,7 @@ impl NodeEpochRewards { } pub fn operator_cost(&self) -> U128 { - U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128) + self.params.operator_cost() } pub fn node_profit(&self) -> U128 { @@ -178,11 +178,15 @@ impl NodeRewardParams { } pub fn operator_cost(&self) -> U128 { - U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128) + self.performance() * U128::from_num(DEFAULT_OPERATOR_INTERVAL_COST) } - pub fn uptime(&self) -> u128 { - self.uptime.u128() + pub fn uptime(&self) -> Uint128 { + self.uptime + } + + pub fn performance(&self) -> U128 { + U128::from_num(self.uptime.u128()) / U128::from_num(100) } pub fn set_reward_blockstamp(&mut self, blockstamp: u64) { @@ -233,7 +237,7 @@ impl RewardParams { } pub fn performance(&self) -> U128 { - U128::from_num(self.node.uptime.u128()) / U128::from_num(100) + self.node.performance() } pub fn set_reward_blockstamp(&mut self, blockstamp: u64) { @@ -256,8 +260,8 @@ impl RewardParams { self.node.reward_blockstamp } - pub fn uptime(&self) -> u128 { - self.node.uptime.u128() + pub fn uptime(&self) -> Uint128 { + self.node.uptime() } pub fn one_over_k(&self) -> U128 { diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 495f3a78d5..2641b2f566 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -449,7 +449,7 @@ pub(crate) fn try_reward_mixnode( let node_delegation = current_bond.total_delegation.amount; // check if it has non-zero uptime - if params.uptime() == 0 { + if params.uptime() == Uint128::zero() { storage::REWARDING_STATUS.save( deps.storage, (epoch.id(), mix_identity.clone()), @@ -1381,7 +1381,7 @@ pub mod tests { let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity) .unwrap() .unwrap(); - let mix_1_uptime = 100; + let mix_1_uptime = 90; let epoch = Interval::init_epoch(env.clone()); save_epoch(&mut deps.storage, &epoch).unwrap(); @@ -1395,7 +1395,7 @@ pub mod tests { params.set_reward_blockstamp(env.block.height); - assert_eq!(params.performance(), U128::from_num(1u32)); + assert_eq!(params.performance(), U128::from_num(0.8999999999999999)); let mix_1_reward_result = mix_1.reward(¶ms); @@ -1407,9 +1407,14 @@ pub mod tests { mix_1_reward_result.lambda(), U128::from_num(0.0000133333333333f64) ); - assert_eq!(mix_1_reward_result.reward().int(), 259114u128); + assert_eq!(mix_1_reward_result.reward().int(), 233202u128); - assert_eq!(mix_1.node_profit(¶ms).int(), 203558u128); + assert_eq!(mix_1.node_profit(¶ms).int(), 183202u128); + + assert_ne!( + mix_1_reward_result.reward(), + mix_1.node_profit(¶ms).int() + ); let mix1_operator_reward = mix_1.operator_reward(¶ms); @@ -1417,9 +1422,9 @@ pub mod tests { let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms); - assert_eq!(mix1_operator_reward, 167513); - assert_eq!(mix1_delegator1_reward, 73280); - assert_eq!(mix1_delegator2_reward, 18320); + assert_eq!(mix1_operator_reward, 150761); + assert_eq!(mix1_delegator1_reward, 65952); + assert_eq!(mix1_delegator2_reward, 16488); assert_eq!( mix_1_reward_result.reward().int(), diff --git a/explorer-api/src/mix_node/econ_stats.rs b/explorer-api/src/mix_node/econ_stats.rs index 25765cf8c6..3c526097d4 100644 --- a/explorer-api/src/mix_node/econ_stats.rs +++ b/explorer-api/src/mix_node/econ_stats.rs @@ -36,6 +36,6 @@ pub(crate) async fn retrieve_mixnode_econ_stats( estimated_total_node_reward: reward_estimation.estimated_total_node_reward, estimated_operator_reward: reward_estimation.estimated_operator_reward, estimated_delegators_reward: reward_estimation.estimated_delegators_reward, - current_interval_uptime: reward_estimation.reward_params.node.uptime() as u8, + current_interval_uptime: reward_estimation.reward_params.node.uptime().u128() as u8, }) } diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 116bb9b190..a03f59661d 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -148,15 +148,13 @@ pub(crate) async fn get_mixnode_reward_estimation( let reward_params = RewardParams::new(reward_params, node_reward_params); match bond.estimate_reward(&reward_params) { - Ok(( - estimated_total_node_reward, - estimated_operator_reward, - estimated_delegators_reward, - )) => { + Ok(reward_estimate) => { let reponse = RewardEstimationResponse { - estimated_total_node_reward, - estimated_operator_reward, - estimated_delegators_reward, + estimated_total_node_reward: reward_estimate.total_node_reward, + estimated_operator_reward: reward_estimate.operator_reward, + estimated_delegators_reward: reward_estimate.delegators_reward, + estimated_node_profit: reward_estimate.node_profit, + estimated_operator_cost: reward_estimate.operator_cost, reward_params, as_at, }; diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs index 46e849f816..d98b5f798d 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/validator-api/validator-api-requests/src/models.rs @@ -58,6 +58,8 @@ pub struct RewardEstimationResponse { pub estimated_total_node_reward: u64, pub estimated_operator_reward: u64, pub estimated_delegators_reward: u64, + pub estimated_node_profit: u64, + pub estimated_operator_cost: u64, pub reward_params: RewardParams, pub as_at: i64,