Fix unbonding for new nodes
This commit is contained in:
@@ -18,4 +18,6 @@ pub enum MixnetContractError {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("Division by zero at {}", line!())]
|
||||
DivisionByZero,
|
||||
}
|
||||
|
||||
@@ -222,11 +222,17 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
if self.sigma == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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 scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
|
||||
// Div by zero checked above
|
||||
let delegator_reward =
|
||||
(ONE - self.profit_margin) * (scaled_delegation_amount / self.sigma) * self.node_profit;
|
||||
|
||||
@@ -465,12 +471,16 @@ impl MixNodeBond {
|
||||
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
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 operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
// Div by zero checked above
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
|
||||
@@ -57,8 +57,12 @@ impl NodeEpochRewards {
|
||||
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
|
||||
let reward = self.node_profit();
|
||||
let operator_base_reward = reward.min(self.operator_cost());
|
||||
let operator_reward =
|
||||
(profit_margin + (ONE - profit_margin) * self.lambda() / self.sigma()) * reward;
|
||||
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
|
||||
value
|
||||
} else {
|
||||
return Err(MixnetContractError::DivisionByZero);
|
||||
};
|
||||
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
|
||||
|
||||
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
|
||||
|
||||
@@ -80,8 +84,15 @@ impl NodeEpochRewards {
|
||||
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward =
|
||||
(ONE - profit_margin) * scaled_delegation_amount / self.sigma() * self.node_profit();
|
||||
|
||||
let check_div_by_zero =
|
||||
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
|
||||
value
|
||||
} else {
|
||||
return Err(MixnetContractError::DivisionByZero);
|
||||
};
|
||||
|
||||
let delegator_reward = (ONE - profit_margin) * check_div_by_zero * self.node_profit();
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
@@ -198,6 +209,11 @@ impl RewardParams {
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if denom == 0 {
|
||||
return U128::ZERO;
|
||||
}
|
||||
|
||||
// Div by zero checked above
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use super::storage::{self, PENDING_DELEGATION_EVENTS};
|
||||
// use crate::contract::debug_with_visibility;
|
||||
// use crate::contract::debug_with_visibility;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
@@ -44,6 +45,8 @@ pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
// debug_with_visibility(api, "Reconciling delegation events");
|
||||
|
||||
for (key, delegation_event) in pending_delegation_events {
|
||||
match delegation_event {
|
||||
DelegationEvent::Delegate(delegation) => {
|
||||
@@ -253,7 +256,7 @@ pub struct ReconcileUndelegateResponse {
|
||||
|
||||
pub(crate) fn try_reconcile_undelegation(
|
||||
storage: &mut dyn Storage,
|
||||
_api: &dyn Api,
|
||||
api: &dyn Api,
|
||||
pending_undelegate: &PendingUndelegate,
|
||||
) -> Result<ReconcileUndelegateResponse, ContractError> {
|
||||
let delegation_map = storage::delegations();
|
||||
@@ -283,6 +286,7 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
|
||||
let reward = crate::rewards::transactions::calculate_delegator_reward(
|
||||
storage,
|
||||
api,
|
||||
pending_undelegate.proxy_storage_key(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
)?;
|
||||
|
||||
@@ -191,6 +191,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn query_operator_reward(deps: Deps, owner: String) -> Result<Uint128, Contr
|
||||
}
|
||||
};
|
||||
|
||||
super::transactions::calculate_operator_reward(deps.storage, &owner_address, &bond)
|
||||
super::transactions::calculate_operator_reward(deps.storage, deps.api, &owner_address, &bond)
|
||||
}
|
||||
|
||||
pub fn query_delegator_reward(
|
||||
@@ -58,7 +58,7 @@ pub fn query_delegator_reward(
|
||||
&deps.api.addr_validate(&owner)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
super::transactions::calculate_delegator_reward(deps.storage, key, &mix_identity)
|
||||
super::transactions::calculate_delegator_reward(deps.storage, deps.api, key, &mix_identity)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::storage::{
|
||||
OPERATOR_REWARD_CLAIMED_HEIGHT,
|
||||
};
|
||||
use crate::constants;
|
||||
use crate::contract::debug_with_visibility;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::_try_delegate_to_mixnode;
|
||||
use crate::error::ContractError;
|
||||
@@ -14,7 +15,7 @@ use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond};
|
||||
use crate::rewards::helpers;
|
||||
use crate::support::helpers::is_authorized;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
|
||||
use cosmwasm_std::{Addr, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
|
||||
use mixnet_contract_common::events::{
|
||||
new_compound_delegator_reward_event, new_compound_operator_reward_event,
|
||||
new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event,
|
||||
@@ -35,8 +36,13 @@ pub fn try_compound_operator_reward_on_behalf(
|
||||
let proxy = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
|
||||
let reward =
|
||||
_try_compound_operator_reward(deps.storage, env.block.height, &owner, Some(proxy))?;
|
||||
let reward = _try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
Some(proxy),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_compound_operator_reward_event(&owner, reward)))
|
||||
}
|
||||
@@ -47,13 +53,15 @@ pub fn try_compound_operator_reward(
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let reward = _try_compound_operator_reward(deps.storage, env.block.height, &owner, None)?;
|
||||
let reward =
|
||||
_try_compound_operator_reward(deps.storage, deps.api, env.block.height, &owner, None)?;
|
||||
|
||||
Ok(Response::new().add_event(new_compound_operator_reward_event(&owner, reward)))
|
||||
}
|
||||
|
||||
pub fn _try_compound_operator_reward(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
block_height: u64,
|
||||
owner: &Addr,
|
||||
proxy: Option<Addr>,
|
||||
@@ -72,7 +80,7 @@ pub fn _try_compound_operator_reward(
|
||||
}
|
||||
|
||||
let mut updated_bond = bond.clone();
|
||||
let reward = calculate_operator_reward(storage, owner, &bond)?;
|
||||
let reward = calculate_operator_reward(storage, api, owner, &bond)?;
|
||||
updated_bond.accumulated_rewards = Some(updated_bond.accumulated_rewards() - reward);
|
||||
updated_bond.pledge_amount.amount += reward;
|
||||
mixnodes().replace(
|
||||
@@ -94,6 +102,7 @@ pub fn _try_compound_operator_reward(
|
||||
|
||||
pub fn calculate_operator_reward(
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
owner: &Addr,
|
||||
bond: &StoredMixnodeBond,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
@@ -111,19 +120,22 @@ pub fn calculate_operator_reward(
|
||||
Ok(Uint128::zero()),
|
||||
|acc, height| -> Result<Uint128, ContractError> {
|
||||
let accumulated_reward = acc?;
|
||||
if let Some(bond) =
|
||||
mixnodes().may_load_at_height(storage, bond.identity().as_str(), height)?
|
||||
if let Some(bond) = mixnodes()
|
||||
.may_load_at_height(storage, bond.identity().as_str(), height)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(ref epoch_rewards) = bond.epoch_rewards {
|
||||
let epoch_reward_params =
|
||||
epoch_reward_params_for_id(storage, epoch_rewards.epoch_id())?;
|
||||
// Compound rewards from previous heights
|
||||
let reward_at_height = epoch_rewards.delegation_reward(
|
||||
bond.pledge_amount().amount + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
epoch_reward_params,
|
||||
)?;
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
match epoch_rewards.operator_reward(bond.profit_margin()) {
|
||||
Ok(reward) => return Ok(accumulated_reward + reward),
|
||||
Err(err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!("Failed to calculate operator reward: {:?}", err),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
Ok(accumulated_reward)
|
||||
@@ -204,7 +216,7 @@ pub fn _try_compound_delegator_reward(
|
||||
&deps.api.addr_validate(owner_address)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(deps.storage, key.clone(), mix_identity)?;
|
||||
let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?;
|
||||
let mut compounded_delegation = reward;
|
||||
|
||||
// Might want to introduce paging here
|
||||
@@ -261,6 +273,7 @@ pub fn _try_compound_delegator_reward(
|
||||
// + last_reward_claimed height is correctly used
|
||||
pub fn calculate_delegator_reward(
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
key: Vec<u8>,
|
||||
mix_identity: &str,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
@@ -296,7 +309,15 @@ pub fn calculate_delegator_reward(
|
||||
.fold(Uint128::zero(), |total, delegation| {
|
||||
total + delegation.amount.amount
|
||||
});
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("delegation at height {} - {}", height, delegation_at_height),
|
||||
// );
|
||||
if delegation_at_height != Uint128::zero() {
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("Loading bond {} at height {}", mix_identity, height),
|
||||
// );
|
||||
if let Some(bond) = mixnodes()
|
||||
.may_load_at_height(storage, mix_identity, height)
|
||||
.ok()
|
||||
@@ -304,17 +325,40 @@ pub fn calculate_delegator_reward(
|
||||
{
|
||||
if let Some(ref epoch_rewards) = bond.epoch_rewards {
|
||||
// Compound rewards from previous heights
|
||||
let epoch_reward_params =
|
||||
epoch_reward_params_for_id(storage, epoch_rewards.epoch_id())?;
|
||||
let reward_at_height = match epoch_rewards.delegation_reward(
|
||||
delegation_at_height + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
epoch_reward_params,
|
||||
) {
|
||||
Ok(reward) => reward,
|
||||
Err(_err) => Uint128::zero(),
|
||||
};
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
match epoch_reward_params_for_id(storage, epoch_rewards.epoch_id()) {
|
||||
Ok(params) => {
|
||||
let reward_at_height = match epoch_rewards.delegation_reward(
|
||||
delegation_at_height + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
params,
|
||||
) {
|
||||
Ok(reward) => {
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("Reward at height {} - {}", height, reward),
|
||||
// );
|
||||
reward
|
||||
}
|
||||
Err(err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!(
|
||||
"Error calculating reward at {} - {}",
|
||||
height, err
|
||||
),
|
||||
);
|
||||
Uint128::zero()
|
||||
}
|
||||
};
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
}
|
||||
Err(_err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!("No epoch reward params for epoch {}", height),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1181,7 +1225,8 @@ pub mod tests {
|
||||
}
|
||||
|
||||
let alice_reward =
|
||||
calculate_delegator_reward(&deps.storage, key.clone(), &node_identity_1).unwrap();
|
||||
calculate_delegator_reward(&deps.storage, &deps.api, key.clone(), &node_identity_1)
|
||||
.unwrap();
|
||||
assert_eq!(alice_reward, Uint128::new(304552));
|
||||
|
||||
let mix_0 = mixnodes.load(&deps.storage, &node_identity_1).unwrap();
|
||||
@@ -1231,8 +1276,9 @@ pub mod tests {
|
||||
);
|
||||
|
||||
let operator_reward =
|
||||
calculate_operator_reward(&deps.storage, &Addr::unchecked("alice"), &mix_1).unwrap();
|
||||
assert_eq!(operator_reward, Uint128::new(190345));
|
||||
calculate_operator_reward(&deps.storage, &deps.api, &Addr::unchecked("alice"), &mix_1)
|
||||
.unwrap();
|
||||
assert_eq!(operator_reward, Uint128::new(352532));
|
||||
|
||||
assert_eq!(
|
||||
mix_1_reward_result.sigma(),
|
||||
|
||||
Reference in New Issue
Block a user