Skip to content

Commit

Permalink
Merge pull request #79 from osmosis-labs/list-all-rewards-per-user
Browse files Browse the repository at this point in the history
List all rewards per user
  • Loading branch information
ethanfrey authored Jun 28, 2023
2 parents bdd9244 + 9764c27 commit caf0552
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 12 deletions.
54 changes: 46 additions & 8 deletions contracts/provider/external-staking/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_std::{
coin, coins, ensure, ensure_eq, from_binary, Addr, BankMsg, Binary, Coin, Decimal, DepsMut,
Env, Order, Response, StdResult, Storage, Uint128, Uint256, WasmMsg,
Env, Order, Response, StdError, StdResult, Storage, Uint128, Uint256, WasmMsg,
};
use cw2::set_contract_version;
use cw_storage_plus::{Bounder, Item, Map};
Expand All @@ -25,9 +25,9 @@ use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};

use crate::error::ContractError;
use crate::msg::{
AllTxsResponse, AuthorizedEndpointResponse, ConfigResponse, IbcChannelResponse,
ListRemoteValidatorsResponse, PendingRewards, ReceiveVirtualStake, StakeInfo, StakesResponse,
TxResponse,
AllPendingRewards, AllTxsResponse, AuthorizedEndpointResponse, ConfigResponse,
IbcChannelResponse, ListRemoteValidatorsResponse, PendingRewards, ReceiveVirtualStake,
StakeInfo, StakesResponse, TxResponse, ValidatorPendingReward,
};
use crate::state::{Config, Distribution, Stake};
use mesh_sync::Tx;
Expand Down Expand Up @@ -800,17 +800,55 @@ impl ExternalStakingContract<'_> {
Ok(resp)
}

/// Returns how much rewards are to be withdrawn by particular user, iterating over all validators.
/// This is like stakes is to stake query, but for rewards.
#[msg(query)]
pub fn all_pending_rewards(
&self,
ctx: QueryCtx,
user: String,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<AllPendingRewards, ContractError> {
let limit: usize = clamp_page_limit(limit);
let user = ctx.deps.api.addr_validate(&user)?;

let bound = start_after.as_deref().and_then(Bounder::exclusive_bound);

let config = self.config.load(ctx.deps.storage)?;

let rewards: Vec<_> = self
.stakes
.prefix(&user)
.range(ctx.deps.storage, bound, None, Order::Ascending)
.take(limit)
.map(|item| {
let (validator, stake_lock) = item?;
let stake = stake_lock.read()?;
let distribution = self
.distribution
.may_load(ctx.deps.storage, &validator)?
.unwrap_or_default();
let amount = Self::calculate_reward(stake, &distribution)?;
Ok::<_, ContractError>(ValidatorPendingReward::new(
validator,
amount.u128(),
&config.rewards_denom,
))
})
.collect::<Result<_, _>>()?;

Ok(AllPendingRewards { rewards })
}

/// Calculates reward for the user basing on the `Stake` he want to withdraw rewards from, and
/// the corresponding validator `Distribution`.
//
// It is important to make sure the distribution passed matches the validator for stake. It
// could be enforced by taking user and validator in arguments, then fetching data, but
// sometimes data are used also for different calculations so we want to avoid double
// fetching.
fn calculate_reward(
stake: &Stake,
distribution: &Distribution,
) -> Result<Uint128, ContractError> {
fn calculate_reward(stake: &Stake, distribution: &Distribution) -> Result<Uint128, StdError> {
let points = distribution.points_per_stake * Uint256::from(stake.stake);

let points = stake.points_alignment.align(points);
Expand Down
25 changes: 23 additions & 2 deletions contracts/provider/external-staking/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, IbcChannel, Uint128};
use cosmwasm_std::{coin, Coin, IbcChannel, Uint128};

use crate::{error::ContractError, state::Config};

Expand Down Expand Up @@ -89,12 +89,33 @@ pub struct UsersResponse {
pub users: Vec<UserInfo>,
}

/// Response for penging rewards query
/// Response for pending rewards query on one validator
#[cw_serde]
pub struct PendingRewards {
pub amount: Coin,
}

/// Response for pending rewards query on all validator
#[cw_serde]
pub struct AllPendingRewards {
pub rewards: Vec<ValidatorPendingReward>,
}

#[cw_serde]
pub struct ValidatorPendingReward {
pub validator: String,
pub amount: Coin,
}

impl ValidatorPendingReward {
pub fn new(validator: impl Into<String>, amount: u128, denom: impl Into<String>) -> Self {
Self {
amount: coin(amount, denom),
validator: validator.into(),
}
}
}

pub type TxResponse = mesh_sync::Tx;

#[cw_serde]
Expand Down
38 changes: 36 additions & 2 deletions contracts/provider/external-staking/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use sylvia::multitest::App;
use crate::contract::cross_staking::test_utils::CrossStakingApi;
use crate::contract::multitest_utils::{CodeId, ExternalStakingContractProxy};
use crate::error::ContractError;
use crate::msg::{AuthorizedEndpoint, ReceiveVirtualStake, StakeInfo};
use crate::msg::{AuthorizedEndpoint, ReceiveVirtualStake, StakeInfo, ValidatorPendingReward};

const OSMO: &str = "osmo";
const STAR: &str = "star";
Expand Down Expand Up @@ -761,7 +761,7 @@ fn distribution() {
.unwrap();

// Only users[0] stakes on validators[1]
// 30 tokens for users[1]
// 30 tokens for users[0]
contract
.distribute_rewards(validators[1].to_owned())
.with_funds(&coins(30, STAR))
Expand Down Expand Up @@ -789,6 +789,22 @@ fn distribution() {
.unwrap();
assert_eq!(rewards.amount.amount.u128(), 0);

// Show all rewards skips validators that were never staked on
let all_rewards = contract
.all_pending_rewards(users[0].to_owned(), None, None)
.unwrap();
let expected = vec![
ValidatorPendingReward::new(validators[0], 20, STAR),
ValidatorPendingReward::new(validators[1], 30, STAR),
];
assert_eq!(all_rewards.rewards, expected);

let all_rewards = contract
.all_pending_rewards(users[1].to_owned(), None, None)
.unwrap();
let expected = vec![ValidatorPendingReward::new(validators[0], 30, STAR)];
assert_eq!(all_rewards.rewards, expected);

// Distributed funds should be on the staking contract
assert_eq!(
app.app()
Expand Down Expand Up @@ -1210,6 +1226,24 @@ fn distribution() {
.unwrap();
assert_eq!(rewards.amount.amount.u128(), 37);

let all_rewards = contract
.all_pending_rewards(users[0].to_owned(), None, None)
.unwrap();
let expected = vec![
ValidatorPendingReward::new(validators[0], 6, STAR),
ValidatorPendingReward::new(validators[1], 2, STAR),
];
assert_eq!(all_rewards.rewards, expected);

let all_rewards = contract
.all_pending_rewards(users[1].to_owned(), None, None)
.unwrap();
let expected = vec![
ValidatorPendingReward::new(validators[0], 33, STAR),
ValidatorPendingReward::new(validators[1], 37, STAR),
];
assert_eq!(all_rewards.rewards, expected);

// And try to withdraw all, previous balances:
contract
.withdraw_rewards(validators[0].to_string())
Expand Down

0 comments on commit caf0552

Please sign in to comment.