Add claim reward to vesting and mixnet contract (#1292)

* Add claim reward to vesting contract

* Add compound and claim methods to nymd client

* Add TrackReward message

* CHANGELOG
This commit is contained in:
Drazen Urch
2022-05-31 11:06:10 +02:00
committed by GitHub
parent 189b83e769
commit 2f4be6dedc
17 changed files with 535 additions and 4 deletions
+3
View File
@@ -9,6 +9,8 @@
- wallet: added support for multiple accounts ([#1265])
- 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`.
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- 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]).
@@ -34,6 +36,7 @@
[#1275]: https://github.com/nymtech/nym/pull/1275
[#1278]: https://github.com/nymtech/nym/pull/1278
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
@@ -786,6 +786,89 @@ impl<C> NymdClient<C> {
.await
}
pub async fn compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundOperatorReward",
vec![],
)
.await
}
pub async fn claim_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimOperatorReward",
vec![],
)
.await
}
pub async fn compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundDelegatorReward",
vec![],
)
.await
}
pub async fn claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimDelegatorReward",
vec![],
)
.await
}
/// Announce a mixnode, paying a fee.
pub async fn bond_mixnode(
&self,
@@ -12,6 +12,28 @@ use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, Vesting
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
@@ -374,4 +396,78 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::ClaimOperatorReward",
vec![],
)
.await
}
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CompoundOperatorReward",
vec![],
)
.await
}
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::ClaimDelegatorReward",
vec![],
)
.await
}
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CompoundDelegatorReward",
vec![],
)
.await
}
}
@@ -23,7 +23,9 @@ pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
// attributes that are used in multiple places
@@ -151,6 +153,11 @@ pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Even
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
@@ -171,6 +178,26 @@ pub fn new_compound_delegator_reward_event(
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_claim_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
@@ -99,6 +99,17 @@ pub enum ExecuteMsg {
},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
ClaimOperatorReward {},
ClaimOperatorRewardOnBehalf {
owner: String,
},
ClaimDelegatorReward {
mix_identity: IdentityKey,
},
ClaimDelegatorRewardOnBehalf {
mix_identity: IdentityKey,
owner: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -20,6 +20,7 @@ pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixno
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -136,3 +137,7 @@ pub fn new_track_gateway_unbond_event() -> Event {
pub fn new_track_undelegation_event() -> Event {
Event::new(TRACK_UNDELEGATION_EVENT_TYPE)
}
pub fn new_track_reward_event() -> Event {
Event::new(TRACK_REWARD_EVENT_TYPE)
}
@@ -49,6 +49,14 @@ impl VestingSpecification {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
TrackReward {
amount: Coin,
address: String,
},
ClaimOperatorReward {},
ClaimDelegatorReward {
mix_identity: String,
},
CompoundDelegatorReward {
mix_identity: String,
},
+26
View File
@@ -283,6 +283,32 @@ pub fn execute(
info,
)
}
ExecuteMsg::ClaimOperatorReward {} => {
crate::rewards::transactions::try_claim_operator_reward(deps, &env, &info)
}
ExecuteMsg::ClaimOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_claim_operator_reward_on_behalf(
deps, &env, &info, owner,
)
}
ExecuteMsg::ClaimDelegatorReward { mix_identity } => {
crate::rewards::transactions::try_claim_delegator_reward(
deps,
&env,
&info,
&mix_identity,
)
}
ExecuteMsg::ClaimDelegatorRewardOnBehalf {
mix_identity,
owner,
} => crate::rewards::transactions::try_claim_delegator_reward_on_behalf(
deps,
&env,
&info,
owner,
&mix_identity,
),
}
}
+2 -2
View File
@@ -153,8 +153,8 @@ pub enum ContractError {
#[from]
source: MixnetContractError,
},
#[error("No rewards to claim for mixnode {identity} for delegate {delegate}")]
NoRewardsToClaim { identity: String, delegate: String },
#[error("No rewards to claim for mixnode {identity} for {address}")]
NoRewardsToClaim { identity: String, address: String },
#[error("Epoch not initialized yet!")]
EpochNotInitialized,
+185 -1
View File
@@ -15,9 +15,13 @@ 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, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
use cosmwasm_std::{
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response,
Storage, Uint128,
};
use cw_storage_plus::Bound;
use mixnet_contract_common::events::{
new_claim_delegator_reward_event, new_claim_operator_reward_event,
new_compound_delegator_reward_event, new_compound_operator_reward_event,
new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event,
new_too_fresh_bond_mix_operator_rewarding_event, new_zero_uptime_mix_operator_rewarding_event,
@@ -27,6 +31,186 @@ use mixnet_contract_common::reward_params::{NodeEpochRewards, NodeRewardParams,
use mixnet_contract_common::{Delegation, IdentityKey, RewardingStatus};
use mixnet_contract_common::RewardingResult;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use vesting_contract_common::one_ucoin;
// All four of the below methods need to do the following things:
// 1. Calculate currently available rewards
// 2. Send the rewards back to whoever claimed them
// 3. Set the LAST_CLAIMED_HEIGHT to the current height
pub fn try_claim_operator_reward(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
) -> Result<Response, ContractError> {
_try_claim_operator_reward(deps.storage, deps.api, env, &info.sender.to_string(), None)
}
pub fn try_claim_operator_reward_on_behalf(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
owner: String,
) -> Result<Response, ContractError> {
_try_claim_operator_reward(
deps.storage,
deps.api,
env,
&owner,
Some(info.sender.clone()),
)
}
fn _try_claim_operator_reward(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
owner: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = api.addr_validate(owner)?;
let bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
};
if proxy != bond.proxy {
return Err(ContractError::ProxyMismatch {
existing: bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
let reward = calculate_operator_reward(storage, api, &owner, &bond)?;
OPERATOR_REWARD_CLAIMED_HEIGHT.save(
storage,
(owner.to_string(), bond.identity().to_string()),
&env.block.height,
)?;
if reward.is_zero() {
return Err(ContractError::NoRewardsToClaim {
identity: bond.identity().to_string(),
address: owner.to_string(),
});
}
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: coins(reward.u128(), DENOM),
};
let mut response = Response::default()
.add_message(return_tokens)
.add_event(new_claim_operator_reward_event(&owner, reward));
if let Some(proxy) = proxy {
let msg = Some(VestingContractExecuteMsg::TrackReward {
address: owner.to_string(),
amount: Coin::new(reward.u128(), DENOM),
});
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(wasm_msg);
}
Ok(response)
}
pub fn _try_claim_delegator_reward(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
owner: &str,
mix_identity: &str,
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
let owner = api.addr_validate(owner)?;
let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref());
let reward = calculate_delegator_reward(storage, api, key.clone(), mix_identity)?;
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
storage,
(key, mix_identity.to_string()),
&env.block.height,
)?;
if reward.is_zero() {
return Err(ContractError::NoRewardsToClaim {
identity: mix_identity.to_string(),
address: owner.to_string(),
});
}
let return_tokens = BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: coins(reward.u128(), DENOM),
};
let mut response =
Response::default()
.add_message(return_tokens)
.add_event(new_claim_delegator_reward_event(
&owner,
&proxy,
reward,
mix_identity,
));
if let Some(proxy) = proxy {
let msg = Some(VestingContractExecuteMsg::TrackReward {
address: owner.to_string(),
amount: Coin::new(reward.u128(), DENOM),
});
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(wasm_msg);
}
Ok(response)
}
pub fn try_claim_delegator_reward_on_behalf(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
owner: String,
mix_identity: &str,
) -> Result<Response, ContractError> {
_try_claim_delegator_reward(
deps.storage,
deps.api,
env,
&owner,
mix_identity,
Some(info.sender.clone()),
)
}
pub fn try_claim_delegator_reward(
deps: DepsMut<'_>,
env: &Env,
info: &MessageInfo,
mix_identity: &str,
) -> Result<Response, ContractError> {
_try_claim_delegator_reward(
deps.storage,
deps.api,
env,
&info.sender.to_string(),
mix_identity,
None,
)
}
pub fn try_compound_operator_reward_on_behalf(
deps: DepsMut,
+40 -1
View File
@@ -13,7 +13,8 @@ use mixnet_contract_common::{Gateway, IdentityKey, MixNode};
use vesting_contract_common::events::{
new_ownership_transfer_event, new_periodic_vesting_account_event,
new_staking_address_update_event, new_track_gateway_unbond_event,
new_track_mixnode_unbond_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
new_vested_coins_withdraw_event,
};
use vesting_contract_common::messages::{
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
@@ -46,6 +47,13 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::TrackReward { amount, address } => {
try_track_reward(deps, info, amount, &address)
}
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
ExecuteMsg::ClaimDelegatorReward { mix_identity } => {
try_claim_delegator_reward(deps, info, mix_identity)
}
ExecuteMsg::CompoundDelegatorReward { mix_identity } => {
try_compound_delegator_reward(mix_identity, info, deps)
}
@@ -278,6 +286,20 @@ pub fn try_track_unbond_mixnode(
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
}
fn try_track_reward(
deps: DepsMut<'_>,
info: MessageInfo,
amount: Coin,
address: &str,
) -> Result<Response, ContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(ContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(address, deps.storage, deps.api)?;
account.track_reward(amount, deps.storage)?;
Ok(Response::new().add_event(new_track_reward_event()))
}
fn try_track_undelegation(
address: &str,
mix_identity: IdentityKey,
@@ -314,6 +336,23 @@ fn try_compound_delegator_reward(
account.try_compound_delegator_reward(mix_identity, deps.storage)
}
fn try_claim_operator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_claim_operator_reward(deps.storage)
}
fn try_claim_delegator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
mix_identity: String,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_claim_delegator_reward(mix_identity, deps.storage)
}
fn try_undelegate_from_mixnode(
mix_identity: IdentityKey,
info: MessageInfo,
@@ -8,6 +8,8 @@ pub trait MixnodeBondingAccount {
storage: &dyn Storage,
) -> Result<Response, ContractError>;
fn try_claim_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_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
storage: &dyn Storage,
) -> Result<Response, ContractError>;
fn try_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
@@ -73,4 +73,5 @@ pub trait VestingAccount {
to_address: Option<Addr>,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
}
@@ -13,6 +13,25 @@ use vesting_contract_common::one_ucoin;
use super::Account;
impl DelegatingAccount for Account {
fn try_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
storage: &dyn Storage,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::ClaimDelegatorRewardOnBehalf {
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_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
@@ -14,6 +14,20 @@ use vesting_contract_common::PledgeData;
use super::Account;
impl MixnodeBondingAccount for Account {
fn try_claim_operator_reward(&self, storage: &dyn Storage) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::ClaimOperatorRewardOnBehalf {
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_compound_operator_reward(
&self,
storage: &dyn Storage,
@@ -8,6 +8,13 @@ use vesting_contract_common::{OriginalVestingResponse, Period};
use super::Account;
impl VestingAccount for Account {
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
let current_balance = self.load_balance(storage)?;
let new_balance = current_balance + amount.amount;
self.save_balance(new_balance, storage)?;
Ok(())
}
fn locked_coins(
&self,
block_time: Option<Timestamp>,