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,