Bugfix/delegation reconcile (#1219)
* Passing proxy value when attempting to compound delegator reward * Do not attempt to delegate reward to mixnode if its zero * Additional guards against sending 0 tokens * Removed sign of sloppiness * Fixes to rewards and delegation events storage * Remove block count check, epoch cannot be advanced while in progress * Add reward compound ops to vesting contract * Migration to remove 0 value delegationns Co-authored-by: durch <durch@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9fb980dc5e
commit
8318002b0a
@@ -264,13 +264,14 @@ impl<C> Client<C> {
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
Ok(self
|
||||
.nymd
|
||||
.get_delegator_rewards(address, mix_identity)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?
|
||||
.u128())
|
||||
}
|
||||
|
||||
@@ -329,6 +329,7 @@ impl<C> NymdClient<C> {
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Uint128, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -336,6 +337,7 @@ impl<C> NymdClient<C> {
|
||||
let request = QueryMsg::QueryDelegatorReward {
|
||||
address,
|
||||
mix_identity,
|
||||
proxy,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.mixnet_contract_address()?, &request)
|
||||
|
||||
@@ -171,6 +171,7 @@ pub enum QueryMsg {
|
||||
QueryDelegatorReward {
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
GetPendingDelegationEvents {
|
||||
owner_address: String,
|
||||
|
||||
@@ -49,6 +49,10 @@ impl VestingSpecification {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: String,
|
||||
},
|
||||
CompoundOperatorReward {},
|
||||
UpdateMixnodeConfig {
|
||||
profit_margin_percent: u8,
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::delegations::queries::query_mixnode_delegation;
|
||||
use crate::delegations::queries::{
|
||||
query_mixnode_delegations_paged, query_pending_delegation_events,
|
||||
};
|
||||
use crate::delegations::storage::delegations;
|
||||
use crate::error::ContractError;
|
||||
use crate::gateways::queries::query_gateways_paged;
|
||||
use crate::gateways::queries::query_owns_gateway;
|
||||
@@ -32,6 +33,7 @@ use crate::rewards::storage as rewards_storage;
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::mixnode::DelegationEvent;
|
||||
use mixnet_contract_common::{
|
||||
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
|
||||
};
|
||||
@@ -364,10 +366,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
QueryMsg::QueryDelegatorReward {
|
||||
address,
|
||||
mix_identity,
|
||||
proxy,
|
||||
} => to_binary(&crate::rewards::queries::query_delegator_reward(
|
||||
deps,
|
||||
address,
|
||||
mix_identity,
|
||||
proxy,
|
||||
)?),
|
||||
QueryMsg::GetPendingDelegationEvents {
|
||||
owner_address,
|
||||
@@ -389,7 +393,35 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
// if there exists any delegation of 0 value, remove it
|
||||
let zero_delegations = delegations()
|
||||
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_k, v)| v.amount.amount == Uint128::zero())
|
||||
.map(|(k, _v)| k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for delegation in &zero_delegations {
|
||||
delegations().remove(deps.storage, delegation.clone())?;
|
||||
}
|
||||
|
||||
// similarly, if there exists any event that is scheduled to insert/remove delegation with 0 value, purge it
|
||||
let delegate_events_to_purge = crate::delegations::storage::PENDING_DELEGATION_EVENTS
|
||||
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_k, v)| match v {
|
||||
DelegationEvent::Delegate(d) => d.amount.amount == Uint128::zero(),
|
||||
_ => false,
|
||||
})
|
||||
.map(|(k, _v)| k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for delegation_event in delegate_events_to_purge {
|
||||
crate::delegations::storage::PENDING_DELEGATION_EVENTS
|
||||
.remove(deps.storage, delegation_event);
|
||||
}
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,12 @@ pub fn try_reconcile_all_delegation_events(
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
_try_reconcile_all_delegation_events(deps.storage, deps.api)
|
||||
_try_reconcile_all_delegation_events(deps.storage)
|
||||
}
|
||||
|
||||
// TODO: Error handling?
|
||||
pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
) -> Result<Response, ContractError> {
|
||||
let pending_delegation_events = PENDING_DELEGATION_EVENTS
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
@@ -46,12 +45,15 @@ pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
for (key, delegation_event) in pending_delegation_events {
|
||||
match delegation_event {
|
||||
DelegationEvent::Delegate(delegation) => {
|
||||
// if for some reason the delegation is zero, don't do anything since it should be a no-op anyway
|
||||
if delegation.amount.amount == Uint128::zero() {
|
||||
continue;
|
||||
}
|
||||
let event = try_reconcile_delegation(storage, delegation)?;
|
||||
response = response.add_event(event);
|
||||
}
|
||||
DelegationEvent::Undelegate(pending_undelegate) => {
|
||||
let undelegate_response =
|
||||
try_reconcile_undelegation(storage, api, &pending_undelegate)?;
|
||||
let undelegate_response = try_reconcile_undelegation(storage, &pending_undelegate)?;
|
||||
response = response.add_event(undelegate_response.event);
|
||||
if let Some(msg) = undelegate_response.bank_msg {
|
||||
response = response.add_message(msg);
|
||||
@@ -240,7 +242,6 @@ pub struct ReconcileUndelegateResponse {
|
||||
|
||||
pub(crate) fn try_reconcile_undelegation(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
pending_undelegate: &PendingUndelegate,
|
||||
) -> Result<ReconcileUndelegateResponse, ContractError> {
|
||||
let delegation_map = storage::delegations();
|
||||
@@ -266,13 +267,10 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
});
|
||||
}
|
||||
|
||||
let reward = crate::rewards::transactions::_try_compound_delegator_reward(
|
||||
pending_undelegate.block_height(),
|
||||
api,
|
||||
let reward = crate::rewards::transactions::calculate_delegator_reward(
|
||||
storage,
|
||||
pending_undelegate.delegate().as_str(),
|
||||
pending_undelegate.proxy_storage_key(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Might want to introduce paging here
|
||||
@@ -318,13 +316,18 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
)?;
|
||||
}
|
||||
|
||||
let bank_msg = BankMsg::Send {
|
||||
to_address: pending_undelegate
|
||||
.proxy()
|
||||
.as_ref()
|
||||
.unwrap_or(&pending_undelegate.delegate())
|
||||
.to_string(),
|
||||
amount: coins(total_delegation.u128(), DENOM),
|
||||
// don't add a bank message if it would have resulted in attempting to send 0 tokens
|
||||
let bank_msg = if total_delegation != Uint128::zero() {
|
||||
Some(BankMsg::Send {
|
||||
to_address: pending_undelegate
|
||||
.proxy()
|
||||
.as_ref()
|
||||
.unwrap_or(&pending_undelegate.delegate())
|
||||
.to_string(),
|
||||
amount: coins(total_delegation.u128(), DENOM),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
@@ -362,7 +365,7 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
);
|
||||
|
||||
Ok(ReconcileUndelegateResponse {
|
||||
bank_msg: Some(bank_msg),
|
||||
bank_msg,
|
||||
wasm_msg,
|
||||
event,
|
||||
})
|
||||
@@ -502,7 +505,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -581,7 +584,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -644,7 +647,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
// let expected = Delegation::new(
|
||||
// delegation_owner.clone(),
|
||||
@@ -699,7 +702,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -720,7 +723,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let delegations = crate::delegations::queries::query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -765,7 +768,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -786,7 +789,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -874,7 +877,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let expected1 = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -939,7 +942,7 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
@@ -969,7 +972,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
@@ -1057,7 +1060,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let _delegation = query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -1127,7 +1130,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
let delegation = query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -1162,7 +1165,7 @@ mod tests {
|
||||
)
|
||||
);
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
assert!(test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
@@ -1195,7 +1198,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
@@ -1205,7 +1208,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
|
||||
// sender1 undelegates
|
||||
try_remove_delegation_from_mixnode(
|
||||
@@ -1216,7 +1219,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
// but total delegation should still equal to what sender2 sent
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
|
||||
@@ -158,4 +158,7 @@ pub enum ContractError {
|
||||
|
||||
#[error("Epoch not initialized yet!")]
|
||||
EpochNotInitialized,
|
||||
|
||||
#[error("Invalid address: {0}")]
|
||||
InvalidAddress(String),
|
||||
}
|
||||
|
||||
@@ -43,16 +43,6 @@ pub fn try_write_rewarded_set(
|
||||
}
|
||||
|
||||
let block_height = env.block.height;
|
||||
|
||||
if let Some(last_update) = storage::CURRENT_REWARDED_SET_HEIGHT.may_load(deps.storage)? {
|
||||
if last_update + crate::constants::REWARDED_SET_REFRESH_BLOCKS > block_height {
|
||||
return Err(ContractError::TooFrequentRewardedSetUpdate {
|
||||
last_update,
|
||||
minimum_delay: crate::constants::REWARDED_SET_REFRESH_BLOCKS,
|
||||
current_height: block_height,
|
||||
});
|
||||
}
|
||||
}
|
||||
let num_nodes = rewarded_set.len();
|
||||
|
||||
storage::save_rewarded_set(deps.storage, block_height, active_set_size, rewarded_set)?;
|
||||
@@ -184,21 +174,6 @@ mod tests {
|
||||
|
||||
// cannot be performed too soon after a previous update
|
||||
env.block.height = last_update + 1;
|
||||
assert_eq!(
|
||||
Err(ContractError::TooFrequentRewardedSetUpdate {
|
||||
last_update,
|
||||
minimum_delay: crate::constants::REWARDED_SET_REFRESH_BLOCKS,
|
||||
current_height: last_update + 1,
|
||||
}),
|
||||
try_write_rewarded_set(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
authorised_sender.clone(),
|
||||
full_rewarded_set.clone(),
|
||||
current_state.params.mixnode_active_set_size
|
||||
)
|
||||
);
|
||||
|
||||
// after successful rewarded set write, all internal storage structures are updated appropriately
|
||||
env.block.height = last_update + crate::constants::REWARDED_SET_REFRESH_BLOCKS;
|
||||
let expected_response = Response::new().add_event(new_change_rewarded_set_event(
|
||||
|
||||
@@ -46,13 +46,19 @@ pub fn query_delegator_reward(
|
||||
deps: Deps,
|
||||
owner: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let owner_address = deps.api.addr_validate(&owner)?;
|
||||
super::transactions::calculate_delegator_reward(
|
||||
deps.storage,
|
||||
owner_address.as_str(),
|
||||
&mix_identity,
|
||||
)
|
||||
let proxy = proxy.map(|p| {
|
||||
deps.api
|
||||
.addr_validate(&p)
|
||||
.map_err(|_| ContractError::InvalidAddress(p))
|
||||
.expect("proxy address is invalid")
|
||||
});
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&deps.api.addr_validate(&owner)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
super::transactions::calculate_delegator_reward(deps.storage, key, &mix_identity)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -14,7 +14,8 @@ pub(crate) const REWARD_POOL: Item<'_, Uint128> = Item::new("pool");
|
||||
// TODO: Do we need a migration for this?
|
||||
pub(crate) const REWARDING_STATUS: Map<'_, (u32, IdentityKey), RewardingStatus> = Map::new("rm");
|
||||
|
||||
pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
|
||||
// This has to be a byte vector due to proxy delegastions and rewarding
|
||||
pub(crate) const DELEGATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Vec<u8>, IdentityKey), BlockHeight> =
|
||||
Map::new("drc");
|
||||
pub(crate) const OPERATOR_REWARD_CLAIMED_HEIGHT: Map<'_, (Address, IdentityKey), BlockHeight> =
|
||||
Map::new("orc");
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{self, epoch_reward_params_for_id, DELEGATOR_REWARD_CLAIMED_HEIGHT};
|
||||
use super::storage::{
|
||||
self, epoch_reward_params_for_id, DELEGATOR_REWARD_CLAIMED_HEIGHT,
|
||||
OPERATOR_REWARD_CLAIMED_HEIGHT,
|
||||
};
|
||||
use crate::constants;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::_try_delegate_to_mixnode;
|
||||
@@ -80,9 +83,9 @@ pub fn _try_compound_operator_reward(
|
||||
block_height,
|
||||
)?;
|
||||
|
||||
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
|
||||
OPERATOR_REWARD_CLAIMED_HEIGHT.save(
|
||||
storage,
|
||||
(bond.identity().to_string(), owner.to_string()),
|
||||
(owner.to_string(), bond.identity().to_string()),
|
||||
&block_height,
|
||||
)?;
|
||||
|
||||
@@ -198,31 +201,36 @@ pub fn _try_compound_delegator_reward(
|
||||
mix_identity: &str,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let reward = calculate_delegator_reward(storage, owner_address, mix_identity)?;
|
||||
if _try_delegate_to_mixnode(
|
||||
storage,
|
||||
api,
|
||||
block_height,
|
||||
mix_identity,
|
||||
owner_address,
|
||||
Coin {
|
||||
amount: reward,
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
proxy,
|
||||
)
|
||||
.is_ok()
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&api.addr_validate(owner_address)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(storage, key.clone(), mix_identity)?;
|
||||
if reward != Uint128::zero()
|
||||
&& _try_delegate_to_mixnode(
|
||||
storage,
|
||||
api,
|
||||
block_height,
|
||||
mix_identity,
|
||||
owner_address,
|
||||
Coin {
|
||||
amount: reward,
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
proxy,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
// Node exists all is well, life goes on, if it does not exist we'll just return the reward to the caller as there is nothing to do on the bond
|
||||
if let Some(mut bond) = mixnodes().may_load(storage, mix_identity)? {
|
||||
bond.accumulated_rewards = Some(bond.accumulated_rewards() - reward);
|
||||
mixnodes().save(storage, mix_identity, &bond, block_height)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
|
||||
storage,
|
||||
(mix_identity.to_string(), owner_address.to_string()),
|
||||
(key, mix_identity.to_string()),
|
||||
&block_height,
|
||||
)?;
|
||||
|
||||
@@ -234,20 +242,17 @@ pub fn _try_compound_delegator_reward(
|
||||
// + last_reward_claimed height is correctly used
|
||||
pub fn calculate_delegator_reward(
|
||||
storage: &dyn Storage,
|
||||
owner_address: &str,
|
||||
key: Vec<u8>,
|
||||
mix_identity: &str,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT
|
||||
.load(
|
||||
storage,
|
||||
(owner_address.to_string(), mix_identity.to_string()),
|
||||
)
|
||||
.load(storage, (key.clone(), mix_identity.to_string()))
|
||||
.unwrap_or(0);
|
||||
|
||||
// Get delegations newer then last_claimed_height, it would be nice to also fold this into the iteration bellow but it should be ok for now, as
|
||||
// I doubt folks refresh their delegations often
|
||||
let delegations = delegations_storage::delegations()
|
||||
.prefix((mix_identity.to_string(), owner_address.as_bytes().to_vec()))
|
||||
.prefix((mix_identity.to_string(), key))
|
||||
.range(storage, None, None, Order::Descending)
|
||||
.filter_map(|record| record.ok())
|
||||
.filter(|(height, _)| last_claimed_height <= *height)
|
||||
@@ -866,11 +871,8 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
@@ -985,11 +987,8 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
@@ -46,6 +46,10 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
|
||||
try_compound_delegator_reward(mix_identity, info, deps)
|
||||
}
|
||||
ExecuteMsg::CompoundOperatorReward {} => try_compound_operator_reward(info, deps),
|
||||
ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
} => try_update_mixnode_config(profit_margin_percent, info, deps),
|
||||
@@ -234,6 +238,14 @@ pub fn try_track_unbond_gateway(
|
||||
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
||||
}
|
||||
|
||||
pub fn try_compound_operator_reward(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_compound_operator_reward(deps.storage)
|
||||
}
|
||||
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -293,6 +305,15 @@ fn try_delegate_to_mixnode(
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
||||
}
|
||||
|
||||
fn try_compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_compound_delegator_reward(mix_identity, deps.storage)
|
||||
}
|
||||
|
||||
fn try_undelegate_from_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
|
||||
@@ -3,6 +3,11 @@ use cosmwasm_std::{Coin, Env, Response, Storage};
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
|
||||
pub trait MixnodeBondingAccount {
|
||||
fn try_compound_operator_reward(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
|
||||
@@ -3,6 +3,12 @@ use cosmwasm_std::{Coin, Env, Response, Storage, Uint128};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
|
||||
pub trait DelegatingAccount {
|
||||
fn try_compound_delegator_reward(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
|
||||
@@ -13,6 +13,24 @@ use vesting_contract_common::one_ucoin;
|
||||
use super::Account;
|
||||
|
||||
impl DelegatingAccount for Account {
|
||||
fn try_compound_delegator_reward(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::CompoundDelegatorRewardOnBehalf {
|
||||
owner: self.owner_address().to_string(),
|
||||
mix_identity,
|
||||
};
|
||||
let compound_delegator_reward_msg = wasm_execute(
|
||||
MIXNET_CONTRACT_ADDRESS.load(storage)?,
|
||||
&msg,
|
||||
vec![one_ucoin()],
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_message(compound_delegator_reward_msg))
|
||||
}
|
||||
|
||||
fn try_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
|
||||
@@ -14,6 +14,23 @@ use vesting_contract_common::PledgeData;
|
||||
use super::Account;
|
||||
|
||||
impl MixnodeBondingAccount for Account {
|
||||
fn try_compound_operator_reward(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::CompoundOperatorRewardOnBehalf {
|
||||
owner: self.owner_address().into_string(),
|
||||
};
|
||||
|
||||
let compound_operator_reward_msg = wasm_execute(
|
||||
MIXNET_CONTRACT_ADDRESS.load(storage)?,
|
||||
&msg,
|
||||
vec![one_ucoin()],
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_message(compound_operator_reward_msg))
|
||||
}
|
||||
|
||||
fn try_update_mixnode_config(
|
||||
&self,
|
||||
profit_margin_percent: u8,
|
||||
|
||||
@@ -71,11 +71,12 @@ pub async fn get_reverse_mix_delegations_paged(
|
||||
pub async fn get_delegator_rewards(
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Uint128, BackendError> {
|
||||
Ok(
|
||||
nymd_client!(state)
|
||||
.get_delegator_rewards(address, mix_identity)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ impl<C> Client<C> {
|
||||
&self,
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
) -> Result<u128, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -164,7 +165,7 @@ impl<C> Client<C> {
|
||||
self.0
|
||||
.read()
|
||||
.await
|
||||
.get_delegator_rewards(address, mix_identity)
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user