Skip to content

Commit

Permalink
feat: add claim_rewards_other extrinsic
Browse files Browse the repository at this point in the history
  • Loading branch information
1xstj committed Jan 29, 2025
1 parent 3d82ceb commit d4e1d6a
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 47 deletions.
13 changes: 6 additions & 7 deletions pallets/rewards/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
//
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.
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;

Expand Down
17 changes: 7 additions & 10 deletions pallets/rewards/src/functions/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
//
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.
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;
Expand Down Expand Up @@ -181,7 +178,7 @@ impl<T: Config> Pallet<T> {
original_apy: Percent,
) -> Option<Percent> {
if deposit_cap.is_zero() {
return None;
return None
}

let propotion = Percent::from_rational(total_deposit, deposit_cap);
Expand All @@ -198,7 +195,7 @@ impl<T: Config> Pallet<T> {
pub fn calculate_reward_per_block(total_reward: BalanceOf<T>) -> Option<BalanceOf<T>> {
let apy_blocks = ApyBlocks::<T>::get();
if apy_blocks.is_zero() {
return None;
return None
}

log::debug!("calculate_reward_per_block : total_reward: {:?}", total_reward);
Expand All @@ -217,7 +214,7 @@ impl<T: Config> Pallet<T> {

// 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::<T>::get();
Expand Down Expand Up @@ -263,7 +260,7 @@ impl<T: Config> Pallet<T> {
let deposit_cap = reward.deposit_cap;

if reward.incentive_cap > total_deposit {
return Err(Error::<T>::TotalDepositLessThanIncentiveCap.into());
return Err(Error::<T>::TotalDepositLessThanIncentiveCap.into())
}

let apy = Self::calculate_propotional_apy(total_deposit, deposit_cap, reward.apy)
Expand Down
27 changes: 25 additions & 2 deletions pallets/rewards/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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<T>,
who: T::AccountId,
asset: Asset<T::AssetId>,
) -> 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
Expand Down
2 changes: 1 addition & 1 deletion pallets/rewards/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ impl tangle_primitives::traits::MultiAssetDelegationInfo<AccountId, Balance, Blo

fn is_operator_active(operator: &AccountId) -> bool {
if operator == &mock_pub_key(10) {
return false;
return false
}
true
}
Expand Down
10 changes: 4 additions & 6 deletions pallets/rewards/src/mock_evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,8 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
len: usize,
) -> Option<Result<(), TransactionValidityError>> {
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,
}
}
Expand All @@ -217,9 +216,8 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
info: Self::SignedInfo,
) -> Option<sp_runtime::DispatchResultWithInfo<sp_runtime::traits::PostDispatchInfoOf<Self>>> {
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,
}
}
Expand Down
61 changes: 52 additions & 9 deletions pallets/rewards/src/tests/claim.rs
Original file line number Diff line number Diff line change
@@ -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},
};
Expand Down Expand Up @@ -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::<Runtime>::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);
});
}
20 changes: 8 additions & 12 deletions pallets/rewards/src/tests/reward_calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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::<Runtime>::calculate_deposit_rewards_with_lock_multiplier(
total_deposit,
Expand Down Expand Up @@ -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::<Runtime>::calculate_deposit_rewards_with_lock_multiplier(
total_deposit,
Expand Down

0 comments on commit d4e1d6a

Please sign in to comment.