diff --git a/contracts/provider/external-staking/src/contract.rs b/contracts/provider/external-staking/src/contract.rs index 25391957..5af47312 100644 --- a/contracts/provider/external-staking/src/contract.rs +++ b/contracts/provider/external-staking/src/contract.rs @@ -26,8 +26,8 @@ use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx}; use crate::error::ContractError; use crate::msg::{ AllPendingRewards, AllTxsResponse, AuthorizedEndpointResponse, ConfigResponse, - IbcChannelResponse, ListRemoteValidatorsResponse, PendingRewards, ReceiveVirtualStake, - StakeInfo, StakesResponse, TxResponse, ValidatorPendingReward, + IbcChannelResponse, ListRemoteValidatorsResponse, MaybePendingRewards, ReceiveVirtualStake, + StakeInfo, StakesResponse, TxResponse, ValidatorPendingRewards, }; use crate::state::{Config, Distribution, Stake}; use mesh_sync::Tx; @@ -776,28 +776,30 @@ impl ExternalStakingContract<'_> { ctx: QueryCtx, user: String, validator: String, - ) -> Result { + ) -> Result { let user = ctx.deps.api.addr_validate(&user)?; let stake_lock = self .stakes .may_load(ctx.deps.storage, (&user, &validator))? .unwrap_or_default(); - 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)?; - let config = self.config.load(ctx.deps.storage)?; + match stake_lock.read() { + Ok(stake) => { + let distribution = self + .distribution + .may_load(ctx.deps.storage, &validator)? + .unwrap_or_default(); - let resp = PendingRewards { - amount: coin(amount.u128(), config.rewards_denom), - }; + let amount = Self::calculate_reward(stake, &distribution)?; + let config = self.config.load(ctx.deps.storage)?; - Ok(resp) + Ok(MaybePendingRewards::Rewards(coin( + amount.u128(), + config.rewards_denom, + ))) + } + Err(_) => Ok(MaybePendingRewards::Locked {}), + } } /// Returns how much rewards are to be withdrawn by particular user, iterating over all validators. @@ -824,17 +826,21 @@ impl ExternalStakingContract<'_> { .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, - )) + match stake_lock.read() { + Ok(stake) => { + let distribution = self + .distribution + .may_load(ctx.deps.storage, &validator)? + .unwrap_or_default(); + let amount = Self::calculate_reward(stake, &distribution)?; + Ok::<_, ContractError>(ValidatorPendingRewards::new( + validator, + amount.u128(), + &config.rewards_denom, + )) + } + Err(_) => Ok(ValidatorPendingRewards::new_locked(validator)), + } }) .collect::>()?; diff --git a/contracts/provider/external-staking/src/msg.rs b/contracts/provider/external-staking/src/msg.rs index 1eb661c8..e8479606 100644 --- a/contracts/provider/external-staking/src/msg.rs +++ b/contracts/provider/external-staking/src/msg.rs @@ -91,27 +91,43 @@ pub struct UsersResponse { /// Response for pending rewards query on one validator #[cw_serde] -pub struct PendingRewards { - pub amount: Coin, +pub enum MaybePendingRewards { + Rewards(Coin), + Locked {}, } +impl MaybePendingRewards { + /// Designed for test code, unwrap or panic if Locked + pub fn unwrap(self) -> Coin { + match self { + MaybePendingRewards::Rewards(coin) => coin, + MaybePendingRewards::Locked {} => panic!("Pending rewards are locked"), + } + } +} /// Response for pending rewards query on all validator #[cw_serde] pub struct AllPendingRewards { - pub rewards: Vec, + pub rewards: Vec, } #[cw_serde] -pub struct ValidatorPendingReward { +pub struct ValidatorPendingRewards { pub validator: String, - pub amount: Coin, + pub rewards: MaybePendingRewards, } -impl ValidatorPendingReward { +impl ValidatorPendingRewards { pub fn new(validator: impl Into, amount: u128, denom: impl Into) -> Self { Self { - amount: coin(amount, denom), validator: validator.into(), + rewards: MaybePendingRewards::Rewards(coin(amount, denom)), + } + } + pub fn new_locked(validator: impl Into) -> Self { + Self { + validator: validator.into(), + rewards: MaybePendingRewards::Locked {}, } } } diff --git a/contracts/provider/external-staking/src/multitest.rs b/contracts/provider/external-staking/src/multitest.rs index 507c8711..c2e1770f 100644 --- a/contracts/provider/external-staking/src/multitest.rs +++ b/contracts/provider/external-staking/src/multitest.rs @@ -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, ValidatorPendingReward}; +use crate::msg::{AuthorizedEndpoint, ReceiveVirtualStake, StakeInfo, ValidatorPendingRewards}; const OSMO: &str = "osmo"; const STAR: &str = "star"; @@ -771,38 +771,42 @@ fn distribution() { // Check how much rewards are pending for withdrawal let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 20); + assert_eq!(rewards.amount.u128(), 20); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 30); + assert_eq!(rewards.amount.u128(), 30); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 30); + assert_eq!(rewards.amount.u128(), 30); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.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), + ValidatorPendingRewards::new(validators[0], 20, STAR), + ValidatorPendingRewards::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)]; + let expected = vec![ValidatorPendingRewards::new(validators[0], 30, STAR)]; assert_eq!(all_rewards.rewards, expected); // Distributed funds should be on the staking contract @@ -834,23 +838,27 @@ fn distribution() { // Check how much rewards are pending for withdrawal let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 48); + assert_eq!(rewards.amount.u128(), 48); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 72); + assert_eq!(rewards.amount.u128(), 72); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 30); + assert_eq!(rewards.amount.u128(), 30); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); // Withdraw rewards contract @@ -876,23 +884,27 @@ fn distribution() { // Rewards should not be withdrawable anymore let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); // Rewads should be on users accounts assert_eq!( @@ -928,13 +940,15 @@ fn distribution() { let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 4); + assert_eq!(rewards.amount.u128(), 4); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 6); + assert_eq!(rewards.amount.u128(), 6); // Now yet another unequal distribution to play around keeping all correct when weights are // changing @@ -995,23 +1009,27 @@ fn distribution() { // Check if messing up with weights didn't affect withdrawable let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 8); + assert_eq!(rewards.amount.u128(), 8); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 12); + assert_eq!(rewards.amount.u128(), 12); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 11); + assert_eq!(rewards.amount.u128(), 11); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); // Now distribute some nice values // 10 on users[0] (~0.4 still not distributed) @@ -1033,23 +1051,27 @@ fn distribution() { let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 18); + assert_eq!(rewards.amount.u128(), 18); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 22); + assert_eq!(rewards.amount.u128(), 22); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 21); + assert_eq!(rewards.amount.u128(), 21); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 30); + assert_eq!(rewards.amount.u128(), 30); // And some more distribution fun - we are 50/50 on validators[1], so distributing odd number of // coins @@ -1065,13 +1087,15 @@ fn distribution() { let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 20); + assert_eq!(rewards.amount.u128(), 20); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 25); + assert_eq!(rewards.amount.u128(), 25); // More unstaking - to make it both ways by both stakers on at least one validator, for sake of // funny error accumulation issues. After two following unstakes, staking on validators[0] is as @@ -1114,13 +1138,15 @@ fn distribution() { let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 28); + assert_eq!(rewards.amount.u128(), 28); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 29); + assert_eq!(rewards.amount.u128(), 29); // Withdraw only by users[0] contract @@ -1136,23 +1162,27 @@ fn distribution() { // Check withdrawals and accounts let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 29); + assert_eq!(rewards.amount.u128(), 29); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 0); + assert_eq!(rewards.amount.u128(), 0); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 30); + assert_eq!(rewards.amount.u128(), 30); // Balances was previously: // 78 on users[0] - now witdrawing 28 from validators[0] and 21 from validators[1] @@ -1208,30 +1238,34 @@ fn distribution() { // Check withdrawals and accounts let rewards = contract .pending_rewards(users[0].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 6); + assert_eq!(rewards.amount.u128(), 6); let rewards = contract .pending_rewards(users[1].to_owned(), validators[0].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 33); + assert_eq!(rewards.amount.u128(), 33); let rewards = contract .pending_rewards(users[0].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 2); + assert_eq!(rewards.amount.u128(), 2); let rewards = contract .pending_rewards(users[1].to_owned(), validators[1].to_owned()) + .unwrap() .unwrap(); - assert_eq!(rewards.amount.amount.u128(), 37); + assert_eq!(rewards.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), + ValidatorPendingRewards::new(validators[0], 6, STAR), + ValidatorPendingRewards::new(validators[1], 2, STAR), ]; assert_eq!(all_rewards.rewards, expected); @@ -1239,8 +1273,8 @@ fn distribution() { .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), + ValidatorPendingRewards::new(validators[0], 33, STAR), + ValidatorPendingRewards::new(validators[1], 37, STAR), ]; assert_eq!(all_rewards.rewards, expected);