diff --git a/pallets/proof-of-stake/src/lib.rs b/pallets/proof-of-stake/src/lib.rs index bafc0ccd8c..8f567cb557 100644 --- a/pallets/proof-of-stake/src/lib.rs +++ b/pallets/proof-of-stake/src/lib.rs @@ -28,7 +28,7 @@ use sp_runtime::{traits::SaturatedConversion, Perbill}; use sp_std::{convert::TryInto, prelude::*}; mod reward_info; -use reward_info::{AsymptoticCurveRewards, RewardInfo, RewardsCalculator}; +use reward_info::{AsymptoticCurveRewards, ConstCurveRewards, RewardInfo, RewardsCalculator}; mod benchmarking; #[cfg(test)] @@ -100,6 +100,7 @@ use super::*; type RewardsDistributionPeriod: Get; type RewardsSchedulesLimit: Get; type MinRewardsPerSession: Get; + type MaxRewardTokensPerPool: Get; type WeightInfo: WeightInfo; type ValuationApi: Valuate; } @@ -127,6 +128,7 @@ use super::*; PoolDoesNotExist, TooManySchedules, TooLittleRewardsPerSession, + PoolRewardTokensLimitExceeded, } #[pallet::event] @@ -150,16 +152,16 @@ use super::*; ValueQuery, >; - // #[pallet::storage] - // pub type UserRewards3rdPartyInfo = StorageDoubleMap< - // _, - // Twox64Concat, - // AccountIdOf, - // Twox64Concat, - // (TokenId, TokenId), - // RewardInfo, - // ValueQuery, - // >; + #[pallet::storage] + pub type UserRewards3rdPartyInfo = StorageDoubleMap< + _, + Twox64Concat, + AccountIdOf, + Twox64Concat, + (TokenId, TokenId), + RewardInfo, + ValueQuery, + >; #[pallet::storage] /// Stores information about pool weight and accumulated rewards. The accumulated @@ -170,13 +172,18 @@ use super::*; StorageValue<_, BTreeMap, ValueQuery>; #[pallet::storage] - pub type PromotedPoolRewards3rdParty = StorageValue<_, BTreeMap, ValueQuery>; + pub type PromotedPoolRewards3rdParty = StorageValue<_, BTreeMap<(TokenId, TokenId), U256>, ValueQuery>; + + // pub type PromotedPoolRewards3rdParty = StorageValue<_, BTreeMap<(TokenId), U256>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn schedules)] pub type RewardsSchedules = StorageValue<_, BoundedBTreeMap<(T::BlockNumber, TokenId, TokenId, Balance, u64), (), T::RewardsSchedulesLimit>, ValueQuery>; + #[pallet::storage] + pub type RewardTokensPerPool = StorageMap<_, Twox64Concat, TokenId, BoundedBTreeMap, ValueQuery>; + #[pallet::storage] pub type ScheduleId = StorageValue<_, u64, ValueQuery>; @@ -322,7 +329,14 @@ use super::*; // TODO: use valuation instead amount directly ensure!(amount_per_session >= T::MinRewardsPerSession::get(), Error::::TooLittleRewardsPerSession); + println!("hello worldd!!!!"); + RewardTokensPerPool::::try_mutate(rewarded_token, |tokens| { + println!("{tokens:?}"); + tokens.try_insert(token_id,()) + }).or(Err(Error::::PoolRewardTokensLimitExceeded))?; + + println!("{rewarded_token} =>>> {:?}",RewardTokensPerPool::::get(rewarded_token)); T::Currency::transfer( token_id.into(), @@ -359,6 +373,50 @@ use super::*; } impl Pallet { + + fn calculate_rewards_amount_3rdparty( + user: AccountIdOf, + liquidity_asset_id: TokenId, + ) -> Result, DispatchError> { + Self::ensure_is_promoted_pool(liquidity_asset_id)?; + + let mut result: sp_std::vec::Vec<_> = Default::default(); + + // TODO: get rid of collect + let reward_tokens = RewardTokensPerPool::::get(liquidity_asset_id) + .iter() + .map(|(x,_)| *x) + .collect::>(); + + + for i in reward_tokens{ + println!(";iittititiitititit"); + + let rewards_info = UserRewards3rdPartyInfo::::try_get(user.clone(), (liquidity_asset_id, i)); + println!("REWARDS INFO {rewards_info:?}"); + if let Ok(info) = rewards_info{ + let current_rewards = match info.activated_amount { + 0 => 0u128, + _ => { + let calc = RewardsCalculator::::new2::( + user.clone(), + liquidity_asset_id, + i + )?; + calc.calculate_rewards().map_err(|err| Into::>::into(err))? + }, + }; + + let total_rewards = current_rewards + .checked_add(info.rewards_not_yet_claimed) + .and_then(|v| v.checked_sub(info.rewards_already_claimed)) + .ok_or(Error::::CalculateRewardsMathError)?; + result.push((i, total_rewards)); + } + } + Ok(result) + + } fn pallet_account() -> T::AccountId { PALLET_ID.into_account_truncating() } @@ -385,6 +443,15 @@ impl Pallet { .ok_or(Error::::NotAPromotedPool)?) } + fn get_pool_rewards_3rdparty(liquidity_asset_id: TokenId, reward_asset_id: TokenId) -> Result { + println!("ret_pool_rewards_3rdparty {liquidity_asset_id} {reward_asset_id}"); + Ok(*PromotedPoolRewards3rdParty::::get() + .get(&(liquidity_asset_id, reward_asset_id)) + //TODO: no error or some dedicated error + .ok_or(Error::::NotAPromotedPool)?) + } + + fn get_current_rewards_time() -> Result { >::block_number() .saturated_into::() @@ -394,9 +461,12 @@ impl Pallet { } fn ensure_is_promoted_pool(liquidity_asset_id: TokenId) -> Result<(), DispatchError> { - if Self::get_pool_rewards(liquidity_asset_id).is_ok() { + println!("ensure_is_promoted_pool {liquidity_asset_id}"); + if Self::get_pool_rewards(liquidity_asset_id).is_ok() || !RewardTokensPerPool::::get(liquidity_asset_id).is_empty() { + println!("ensure_is_promoted_pool ok"); Ok(()) } else { + println!("ensure_is_promoted_pool err"); Err(DispatchError::from(Error::::NotAPromotedPool)) } } @@ -407,25 +477,37 @@ impl Pallet { liquidity_assets_added: Balance, ) -> DispatchResult { Self::ensure_is_promoted_pool(liquidity_asset_id)?; - let current_time: u32 = Self::get_current_rewards_time()?; - let pool_ratio_current = Self::get_pool_rewards(liquidity_asset_id)?; - let default_rewards = RewardInfo { - activated_amount: 0_u128, - rewards_not_yet_claimed: 0_u128, - rewards_already_claimed: 0_u128, - last_checkpoint: current_time, - pool_ratio_at_last_checkpoint: pool_ratio_current, - missing_at_last_checkpoint: U256::from(0u128), - }; + println!("set_liquidity_minting_checkpoint1"); + + { + let reward_tokens = RewardTokensPerPool::::get(liquidity_asset_id) + .iter() + .map(|(x,_)| *x) + // NOTE: get rid of collect + .collect::>(); + + + for i in reward_tokens{ + let calc = RewardsCalculator::::new2::( + user.clone(), + liquidity_asset_id, + i + )?; + + let rewards_info = calc + .activate_more(liquidity_assets_added) + .map_err(|err| Into::>::into(err))?; + + UserRewards3rdPartyInfo::::insert(user.clone(), (liquidity_asset_id,i), rewards_info); + } + } + println!("set_liquidity_minting_checkpoint2"); - // Curved rewards { - let mut rewards_info = RewardsInfo::::try_get(user.clone(), liquidity_asset_id) - .unwrap_or(default_rewards); let calc = RewardsCalculator::::new::( + user.clone(), liquidity_asset_id, - rewards_info, )?; let rewards_info = calc .activate_more(liquidity_assets_added) @@ -435,11 +517,15 @@ impl Pallet { } + println!("set_liquidity_minting_checkpoint3"); + TotalActivatedLiquidity::::try_mutate(liquidity_asset_id, |active_amount| { if let Some(val) = active_amount.checked_add(liquidity_assets_added) { + println!("!!!!!!!!!!!!!!!!!!!!!!!!!"); *active_amount = val; Ok(()) } else { + println!("$$$$$$$$$$$$$$$$$$$$$$$$$"); Err(()) } }) @@ -465,8 +551,8 @@ impl Pallet { ); let calc = RewardsCalculator::::new::( + user.clone(), liquidity_asset_id, - rewards_info, )?; let rewards_info = calc .activate_less(liquidity_assets_burned) @@ -532,8 +618,8 @@ impl ProofOfStakeRewardsApi for Pallet { .or(Err(DispatchError::from(Error::::MissingRewardsInfoError)))?; let calc = RewardsCalculator::::new::( + user.clone(), liquidity_asset_id, - rewards_info.clone(), )?; let current_rewards = calc.calculate_rewards().map_err(|err| Into::>::into(err))?; @@ -572,6 +658,7 @@ impl ProofOfStakeRewardsApi for Pallet { use_balance_from: Option, ) -> DispatchResult { Self::ensure_is_promoted_pool(liquidity_asset_id)?; + println!("activate_liquidity"); ensure!( ::ActivationReservesProvider::can_activate( liquidity_asset_id, @@ -613,6 +700,7 @@ impl ProofOfStakeRewardsApi for Pallet { Ok(()) } + fn calculate_rewards_amount( user: AccountIdOf, liquidity_asset_id: TokenId, @@ -625,8 +713,8 @@ impl ProofOfStakeRewardsApi for Pallet { 0 => 0u128, _ => { let calc = RewardsCalculator::::new::( + user.clone(), liquidity_asset_id, - rewards_info.clone(), )?; calc.calculate_rewards().map_err(|err| Into::>::into(err))? }, @@ -649,7 +737,7 @@ impl LiquidityMiningApi for Pallet { let it = schedules .iter() .filter_map(|((session, rewarded_token, tokenid, amount, _),())|{ - if (*session).saturated_into::() <= Self::session_index() { + if (*session).saturated_into::() > Self::session_index() { Some((rewarded_token, tokenid, amount)) } else { None @@ -657,17 +745,23 @@ impl LiquidityMiningApi for Pallet { }); for (staked_token, token, amount) in it { - let activated_amount = Self::total_activated_amount(token); - let rewards = pools.get(token).cloned().unwrap_or_default(); + println!("STAKED TOKEN: {staked_token} TOKEN: {token} AMOUNT: {amount}"); + + let activated_amount = Self::total_activated_amount(staked_token); + println!("ACTIVATED AMOUNT: {activated_amount}"); + // NOTE: fix + let rewards = pools.get(&(*staked_token, *token)).cloned().unwrap_or_default(); let rewards_for_liquidity = U256::from(*amount) .checked_mul(U256::from(u128::MAX)) .and_then(|x| x.checked_div(activated_amount.into())) .and_then(|x| x.checked_add(rewards)); if let Some(val) = rewards_for_liquidity { - pools.insert(*token, val); + pools.insert((*staked_token,*token), val); } } + println!("STORING POOLS: {pools:?}"); + PromotedPoolRewards3rdParty::::put(pools); let _ = PromotedPoolRewards::::try_mutate(|promoted_pools| -> DispatchResult { diff --git a/pallets/proof-of-stake/src/mock.rs b/pallets/proof-of-stake/src/mock.rs index 651feb810d..2bd2de9286 100644 --- a/pallets/proof-of-stake/src/mock.rs +++ b/pallets/proof-of-stake/src/mock.rs @@ -250,6 +250,7 @@ impl pos::Config for Test { type RewardsDistributionPeriod = ConstU32<10>; type RewardsSchedulesLimit = ConstU32<10>; type MinRewardsPerSession = ConstU128<10>; + type MaxRewardTokensPerPool = ConstU32<5>; type WeightInfo = (); type ValuationApi = MockValuationApi; } diff --git a/pallets/proof-of-stake/src/reward_info.rs b/pallets/proof-of-stake/src/reward_info.rs index 00f1aec643..0e042cb004 100644 --- a/pallets/proof-of-stake/src/reward_info.rs +++ b/pallets/proof-of-stake/src/reward_info.rs @@ -44,27 +44,78 @@ pub struct RewardsCalculator { _curve: sp_std::marker::PhantomData, } -impl RewardsCalculator { +impl RewardsCalculator { pub fn new( + user: T::AccountId, asset_id: TokenId, - rewards_info: RewardInfo, ) -> sp_std::result::Result { + let current_time: u32 = Pallet::::get_current_rewards_time()?; + let pool_ratio_current = Pallet::::get_pool_rewards(asset_id)?; + let default_rewards = RewardInfo { + activated_amount: 0_u128, + rewards_not_yet_claimed: 0_u128, + rewards_already_claimed: 0_u128, + last_checkpoint: current_time, + pool_ratio_at_last_checkpoint: pool_ratio_current, + missing_at_last_checkpoint: U256::from(0u128), + }; + + let rewards_info = crate::RewardsInfo::::try_get(user.clone(), asset_id) + .unwrap_or(default_rewards); + Ok(Self { rewards_context: RewardsContext { current_time: Pallet::::get_current_rewards_time()?, pool_ratio_current: Pallet::::get_pool_rewards(asset_id)?, }, rewards_info, - _curve: PhantomData::, + _curve: PhantomData::, }) } } +impl RewardsCalculator { + pub fn new2( + user: T::AccountId, + asset_id: TokenId, + reward_asset_id: TokenId, + ) -> sp_std::result::Result { + + let current_time: u32 = Pallet::::get_current_rewards_time()?; + + // TODO: do not ignore error + let pool_ratio_current = Pallet::::get_pool_rewards_3rdparty(asset_id, reward_asset_id).unwrap_or_default(); + let default_rewards = RewardInfo { + activated_amount: 0_u128, + rewards_not_yet_claimed: 0_u128, + rewards_already_claimed: 0_u128, + last_checkpoint: current_time, + pool_ratio_at_last_checkpoint: pool_ratio_current, + missing_at_last_checkpoint: U256::from(0u128), + }; + + let rewards_info = crate::RewardsInfo::::try_get(user.clone(), asset_id) + .unwrap_or(default_rewards); + + Ok(Self { + rewards_context: RewardsContext { + current_time: Pallet::::get_current_rewards_time()?, + pool_ratio_current, + }, + rewards_info, + _curve: PhantomData::, + }) + + } +} + + pub trait CurveRewards { fn calculate_curve_position(ctx: &RewardsContext, user_info: &RewardInfo) -> Option; fn calculate_curve_rewards(ctx: &RewardsContext, user_info: &RewardInfo) -> Option; } +pub struct ConstCurveRewards(RewardsContext, RewardInfo); pub struct AsymptoticCurveRewards(RewardsContext, RewardInfo); impl CurveRewards for AsymptoticCurveRewards { @@ -121,6 +172,26 @@ impl CurveRewards for AsymptoticCurveRewards { } } +impl CurveRewards for ConstCurveRewards { + fn calculate_curve_position(ctx: &RewardsContext, user_info: &RewardInfo) -> Option { + Some( U256::from(0) ) + } + + fn calculate_curve_rewards(ctx: &RewardsContext, user_info: &RewardInfo) -> Option { + println!("context: {:?}", ctx.pool_ratio_current); + let pool_rewards_ratio_new = + ctx.pool_ratio_current.checked_sub(user_info.pool_ratio_at_last_checkpoint)?; + let rewards_base: U256 = U256::from(user_info.activated_amount) + .checked_mul(pool_rewards_ratio_new)? + .checked_div(U256::from(u128::MAX))?; // always fit into u128 + println!("BASE : {rewards_base}"); + + rewards_base + .try_into() + .ok() + } +} + #[derive(Debug)] pub enum RewardsCalcError { CheckpointMathError, diff --git a/pallets/proof-of-stake/src/tests.rs b/pallets/proof-of-stake/src/tests.rs index c11a2fac4e..b3f1373736 100644 --- a/pallets/proof-of-stake/src/tests.rs +++ b/pallets/proof-of-stake/src/tests.rs @@ -1105,6 +1105,8 @@ fn claim_rewards_from_pool_that_has_been_disabled() { const MILLION: u128 = 1_000_000; const ALICE: u128 = 2; const BOB: u128 = 3; +const CHARLIE: u128 = 4; +const EVE: u128 = 5; #[test] #[serial] @@ -1310,23 +1312,78 @@ fn reject_schedule_with_too_little_rewards_per_session() { }); } +#[test] +#[serial] +fn number_of_reward_tokens_per_pool_is_limited() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let valuation_mock = MockValuationApi::get_liquidity_asset_context(); + valuation_mock.expect().return_const(Ok(10u32)); + + let pair: (TokenId, TokenId) = (0u32.into(), 4u32.into()); + let amount = 10_000u128; + + for _ in 0..<::MaxRewardTokensPerPool as sp_core::Get>::get() { + let token_id = TokensOf::::create(&ALICE, MILLION).unwrap(); + assert_ok!( + ProofOfStake::reward_pool(RuntimeOrigin::signed(ALICE), pair, token_id, amount, 5u32.into()) + ); + } + + let token_id = TokensOf::::create(&ALICE, MILLION).unwrap(); + assert_err!( + ProofOfStake::reward_pool(RuntimeOrigin::signed(ALICE), pair, token_id, amount, 5u32.into()), + Error::::PoolRewardTokensLimitExceeded + ); + + + + }); +} + #[test] #[serial] fn user_can_claim_3rdparty_rewards() { new_test_ext().execute_with(|| { System::set_block_number(1); + const LIQUIDITY_TOKEN : u32 = 5; let valuation_mock = MockValuationApi::get_liquidity_asset_context(); - valuation_mock.expect().return_const(Ok(10u32)); + valuation_mock.expect().return_const(Ok(LIQUIDITY_TOKEN)); + + assert_eq!(0, TokensOf::::create(&ALICE, 0).unwrap()); + assert_eq!(1, TokensOf::::create(&ALICE, 0).unwrap()); + assert_eq!(2, TokensOf::::create(&ALICE, 0).unwrap()); + assert_eq!(3, TokensOf::::create(&ALICE, 0).unwrap()); + assert_eq!(4, TokensOf::::create(&ALICE, 0).unwrap()); + assert_eq!(5, TokensOf::::create(&ALICE, 0).unwrap()); let token_id = TokensOf::::create(&ALICE, MILLION).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &BOB, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &CHARLIE, 100).unwrap(); + TokensOf::::mint(LIQUIDITY_TOKEN, &EVE, 100).unwrap(); + let pair: (TokenId, TokenId) = (0u32.into(), 4u32.into()); let amount = 10_000u128; + ProofOfStake::update_pool_promotion(RuntimeOrigin::root(), LIQUIDITY_TOKEN, 1u8).unwrap(); + ProofOfStake::reward_pool(RuntimeOrigin::signed(ALICE), pair, token_id, amount, 10u32.into()).unwrap(); + ProofOfStake::activate_liquidity(RuntimeOrigin::signed(BOB), LIQUIDITY_TOKEN, 100, None).unwrap(); + + roll_to_session(1); + assert_eq!( + ProofOfStake::calculate_rewards_amount_3rdparty(BOB, LIQUIDITY_TOKEN, ), + Ok(vec![(token_id, 1000)]) + ); - assert_ok!( - ProofOfStake::reward_pool(RuntimeOrigin::signed(ALICE), pair, token_id, amount, 5u32.into()) + roll_to_session(2); + assert_eq!( + ProofOfStake::calculate_rewards_amount_3rdparty(BOB, LIQUIDITY_TOKEN, ), + Ok(vec![(token_id, 2000)]) ); + + + }); }