From d4e1d6a9ff305200bd5c7a30762804eb44a58cea Mon Sep 17 00:00:00 2001 From: 1xstj <106580853+1xstj@users.noreply.github.com> Date: Thu, 30 Jan 2025 00:53:41 +0400 Subject: [PATCH] feat: add claim_rewards_other extrinsic --- pallets/rewards/src/functions/mod.rs | 13 +++-- pallets/rewards/src/functions/rewards.rs | 17 +++---- pallets/rewards/src/lib.rs | 27 ++++++++++- pallets/rewards/src/mock.rs | 2 +- pallets/rewards/src/mock_evm.rs | 10 ++-- pallets/rewards/src/tests/claim.rs | 61 ++++++++++++++++++++---- pallets/rewards/src/tests/reward_calc.rs | 20 ++++---- 7 files changed, 103 insertions(+), 47 deletions(-) diff --git a/pallets/rewards/src/functions/mod.rs b/pallets/rewards/src/functions/mod.rs index 1bf7c4c20..c7d9fa947 100644 --- a/pallets/rewards/src/functions/mod.rs +++ b/pallets/rewards/src/functions/mod.rs @@ -13,13 +13,12 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use crate::RewardVaultsPotAccount; -use crate::SubaccountType; -use crate::{AssetLookupRewardVaults, Config, Error, Pallet, RewardVaults}; -use frame_support::ensure; -use frame_support::traits::Get; -use sp_runtime::traits::AccountIdConversion; -use sp_runtime::{DispatchError, DispatchResult}; +use crate::{ + AssetLookupRewardVaults, Config, Error, Pallet, RewardVaults, RewardVaultsPotAccount, + SubaccountType, +}; +use frame_support::{ensure, traits::Get}; +use sp_runtime::{traits::AccountIdConversion, DispatchError, DispatchResult}; use sp_std::vec::Vec; use tangle_primitives::services::Asset; diff --git a/pallets/rewards/src/functions/rewards.rs b/pallets/rewards/src/functions/rewards.rs index 4f3b79aa6..44f82dd7e 100644 --- a/pallets/rewards/src/functions/rewards.rs +++ b/pallets/rewards/src/functions/rewards.rs @@ -13,13 +13,10 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use crate::DecayRate; -use crate::DecayStartPeriod; -use crate::RewardVaultsPotAccount; use crate::{ - ApyBlocks, AssetLookupRewardVaults, BalanceOf, Config, Error, Event, Pallet, - RewardConfigForAssetVault, RewardConfigStorage, TotalRewardVaultDeposit, TotalRewardVaultScore, - UserClaimedReward, + ApyBlocks, AssetLookupRewardVaults, BalanceOf, Config, DecayRate, DecayStartPeriod, Error, + Event, Pallet, RewardConfigForAssetVault, RewardConfigStorage, RewardVaultsPotAccount, + TotalRewardVaultDeposit, TotalRewardVaultScore, UserClaimedReward, }; use frame_support::{ensure, traits::Currency}; use frame_system::pallet_prelude::BlockNumberFor; @@ -181,7 +178,7 @@ impl Pallet { original_apy: Percent, ) -> Option { if deposit_cap.is_zero() { - return None; + return None } let propotion = Percent::from_rational(total_deposit, deposit_cap); @@ -198,7 +195,7 @@ impl Pallet { pub fn calculate_reward_per_block(total_reward: BalanceOf) -> Option> { let apy_blocks = ApyBlocks::::get(); if apy_blocks.is_zero() { - return None; + return None } log::debug!("calculate_reward_per_block : total_reward: {:?}", total_reward); @@ -217,7 +214,7 @@ impl Pallet { // If we haven't reached the decay period yet, no decay if blocks_since_last_claim <= start_period { - return Percent::from_percent(100); + return Percent::from_percent(100) } let decay_rate = DecayRate::::get(); @@ -263,7 +260,7 @@ impl Pallet { let deposit_cap = reward.deposit_cap; if reward.incentive_cap > total_deposit { - return Err(Error::::TotalDepositLessThanIncentiveCap.into()); + return Err(Error::::TotalDepositLessThanIncentiveCap.into()) } let apy = Self::calculate_propotional_apy(total_deposit, deposit_cap, reward.apy) diff --git a/pallets/rewards/src/lib.rs b/pallets/rewards/src/lib.rs index a38ba8f11..a019ece64 100644 --- a/pallets/rewards/src/lib.rs +++ b/pallets/rewards/src/lib.rs @@ -89,8 +89,7 @@ pub mod pallet { PalletId, }; use frame_system::pallet_prelude::*; - use sp_runtime::traits::AccountIdConversion; - use sp_runtime::Percent; + use sp_runtime::{traits::AccountIdConversion, Percent}; use tangle_primitives::rewards::LockMultiplier; #[pallet::config] @@ -319,6 +318,30 @@ pub mod pallet { Ok(()) } + /// Claim rewards for another account + /// + /// The dispatch origin must be signed. + /// + /// Parameters: + /// - `who`: The account to claim rewards for + /// - `asset`: The asset to claim rewards for + /// + /// Emits `RewardsClaimed` event when successful. + #[pallet::call_index(6)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn claim_rewards_other( + origin: OriginFor, + who: T::AccountId, + asset: Asset, + ) -> DispatchResult { + ensure_signed(origin)?; + + // calculate and payout rewards for the specified account + Self::calculate_and_payout_rewards(&who, asset)?; + + Ok(()) + } + /// Manage asset id to vault rewards. /// /// # Permissions diff --git a/pallets/rewards/src/mock.rs b/pallets/rewards/src/mock.rs index 8e887a6ef..9d53cf81d 100644 --- a/pallets/rewards/src/mock.rs +++ b/pallets/rewards/src/mock.rs @@ -273,7 +273,7 @@ impl tangle_primitives::traits::MultiAssetDelegationInfo bool { if operator == &mock_pub_key(10) { - return false; + return false } true } diff --git a/pallets/rewards/src/mock_evm.rs b/pallets/rewards/src/mock_evm.rs index 93538421d..0a5e26860 100644 --- a/pallets/rewards/src/mock_evm.rs +++ b/pallets/rewards/src/mock_evm.rs @@ -205,9 +205,8 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { len: usize, ) -> Option> { match self { - RuntimeCall::Ethereum(call) => { - call.pre_dispatch_self_contained(info, dispatch_info, len) - }, + RuntimeCall::Ethereum(call) => + call.pre_dispatch_self_contained(info, dispatch_info, len), _ => None, } } @@ -217,9 +216,8 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall { info: Self::SignedInfo, ) -> Option>> { match self { - call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => { - Some(call.dispatch(RuntimeOrigin::from(RawOrigin::EthereumTransaction(info)))) - }, + call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => + Some(call.dispatch(RuntimeOrigin::from(RawOrigin::EthereumTransaction(info)))), _ => None, } } diff --git a/pallets/rewards/src/tests/claim.rs b/pallets/rewards/src/tests/claim.rs index c283905bc..924013ca7 100644 --- a/pallets/rewards/src/tests/claim.rs +++ b/pallets/rewards/src/tests/claim.rs @@ -1,16 +1,12 @@ -use crate::AssetAction; -use crate::BalanceOf; -use crate::RewardConfigForAssetVault; -use crate::UserClaimedReward; use crate::{ - mock::*, tests::reward_calc::setup_test_env, DecayRate, DecayStartPeriod, Error, - Pallet as RewardsPallet, TotalRewardVaultDeposit, TotalRewardVaultScore, + mock::*, tests::reward_calc::setup_test_env, AssetAction, BalanceOf, DecayRate, + DecayStartPeriod, Error, Pallet as RewardsPallet, RewardConfigForAssetVault, + TotalRewardVaultDeposit, TotalRewardVaultScore, UserClaimedReward, }; -use frame_support::assert_noop; -use frame_support::{assert_ok, traits::Currency}; +use frame_support::{assert_noop, assert_ok, traits::Currency}; use sp_runtime::Percent; -use tangle_primitives::rewards::UserDepositWithLocks; use tangle_primitives::{ + rewards::UserDepositWithLocks, services::Asset, types::rewards::{LockInfo, LockMultiplier}, }; @@ -506,3 +502,50 @@ fn test_claim_frequency_with_decay() { assert!(difference_percent < 1); }); } + +#[test] +fn test_claim_rewards_other() { + new_test_ext().execute_with(|| { + let account: AccountId = AccountId::new([1u8; 32]); + let other_account: AccountId = AccountId::new([2u8; 32]); + let vault_id = 1u32; + let asset = Asset::Custom(1); + let user_deposit = 10_000 * EIGHTEEN_DECIMALS; // 10k tokens + + setup_vault(account.clone(), vault_id, asset).unwrap(); + + // Mock deposit with only unlocked amount + MOCK_DELEGATION_INFO.with(|m| { + m.borrow_mut().deposits.insert( + (account.clone(), asset), + UserDepositWithLocks { unlocked_amount: user_deposit, amount_with_locks: None }, + ); + }); + + // Initial balance should be 0 + assert_eq!(Balances::free_balance(&account), 0); + + // Run to block 1000 + run_to_block(1000); + + // Claim rewards for account from account 2 + assert_ok!(RewardsPallet::::claim_rewards_other( + RuntimeOrigin::signed(other_account.clone()), + account.clone(), + asset + )); + + // Check that rewards were received + let balance = Balances::free_balance(&account); + + // Verify approximate expected rewards (19 tokens with some precision loss) + let expected_reward = 191 * EIGHTEEN_DECIMALS / 10; + let diff = if balance > expected_reward { + balance - expected_reward + } else { + expected_reward - balance + }; + println!("diff: {:?} {:?}", diff, diff / EIGHTEEN_DECIMALS); + assert!(diff <= 2 * EIGHTEEN_DECIMALS); + }); +} diff --git a/pallets/rewards/src/tests/reward_calc.rs b/pallets/rewards/src/tests/reward_calc.rs index e06c9eff2..353ddad9a 100644 --- a/pallets/rewards/src/tests/reward_calc.rs +++ b/pallets/rewards/src/tests/reward_calc.rs @@ -5,8 +5,7 @@ use sp_runtime::Percent; use tangle_primitives::types::rewards::{LockInfo, LockMultiplier, UserDepositWithLocks}; // Mock values for consistent testing -use crate::DecayRate; -use crate::DecayStartPeriod; +use crate::{DecayRate, DecayStartPeriod}; const EIGHTEEN_DECIMALS: u128 = 1_000_000_000_000_000_000_000; const MOCK_DEPOSIT_CAP: u128 = 1_000_000 * EIGHTEEN_DECIMALS; // 1M tokens with 18 decimals const MOCK_TOTAL_ISSUANCE: u128 = 100_000_000 * EIGHTEEN_DECIMALS; // 100M tokens with 18 decimals @@ -371,12 +370,11 @@ fn test_calculate_rewards_with_multiple_claims() { // 1. User's total score = 10k (unlocked) + (10k * 2) (locked) = 30k // 2. User's proportion = 30k / 200k = 15% // 3. APY = 10% = 0.1 tokens per token per year - // 4. Rewards per block = (Total deposit * APY) / blocks_per_year - // = (100k * 0.1) / 3504 ≈ 2.85388127853881278 tokens/block - // 5. User reward per block = 2.85388127853881278 * 15% - // = 0.428538127853881278 tokens/block - // 6. Total reward for 1000 blocks = 0.428538127853881278 * 1000 - // = 28.538812785388127853 tokens + // 4. Rewards per block = (Total deposit * APY) / blocks_per_year = (100k * 0.1) / 3504 ≈ + // 2.85388127853881278 tokens/block + // 5. User reward per block = 2.85388127853881278 * 15% = 0.428538127853881278 tokens/block + // 6. Total reward for 1000 blocks = 0.428538127853881278 * 1000 = 28.538812785388127853 + // tokens System::set_block_number(1000); let result1 = RewardsPallet::::calculate_deposit_rewards_with_lock_multiplier( total_deposit, @@ -428,10 +426,8 @@ fn test_calculate_rewards_with_multiple_claims() { // 1. User's total score = 10k + 10k (unlocked + locked without multiplier) // 2. User's proportion = 20k / 200k = 10% // 3. Same APY and rewards per block - // 4. User reward per block = 1.9 * 10% - // = 0.019 tokens/block - // 5. Total reward for 1000 blocks = 0.019 * 1000 - // = 19.25 tokens + // 4. User reward per block = 1.9 * 10% = 0.019 tokens/block + // 5. Total reward for 1000 blocks = 0.019 * 1000 = 19.25 tokens System::set_block_number(4000); let result4 = RewardsPallet::::calculate_deposit_rewards_with_lock_multiplier( total_deposit,