Skip to content

Commit

Permalink
feat: Rewards pallet (#867)
Browse files Browse the repository at this point in the history
  • Loading branch information
1xstj authored Jan 8, 2025
1 parent 823d6c9 commit 0b826fc
Show file tree
Hide file tree
Showing 59 changed files with 4,355 additions and 585 deletions.
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pallet-services-rpc = { path = "pallets/services/rpc" }
pallet-multi-asset-delegation = { path = "pallets/multi-asset-delegation", default-features = false }
pallet-tangle-lst-benchmarking = { path = "pallets/tangle-lst/benchmarking", default-features = false }
pallet-oracle = { path = "pallets/oracle", default-features = false }
pallet-rewards = { path = "pallets/rewards", default-features = false }

k256 = { version = "0.13.3", default-features = false }
p256 = { version = "0.13.2", default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions node/tests/evm_restaking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ fn operator_join_delegator_delegate_erc20() {

// Deposit and delegate
let deposit_result = precompile
.deposit(U256::ZERO, *usdc.address(), delegate_amount)
.deposit(U256::ZERO, *usdc.address(), delegate_amount, 0)
.from(bob.address())
.send()
.await?
Expand Down Expand Up @@ -314,7 +314,7 @@ fn operator_join_delegator_delegate_asset_id() {

// Deposit and delegate using asset ID
let deposit_result = precompile
.deposit(U256::from(t.usdc_asset_id), Address::ZERO, U256::from(delegate_amount))
.deposit(U256::from(t.usdc_asset_id), Address::ZERO, U256::from(delegate_amount), 0)
.from(bob.address())
.send()
.await?
Expand Down
77 changes: 3 additions & 74 deletions pallets/multi-asset-delegation/fuzzer/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ use frame_system::ensure_signed_or_root;
use honggfuzz::fuzz;
use pallet_multi_asset_delegation::{mock::*, pallet as mad, types::*};
use rand::{seq::SliceRandom, Rng};
use sp_runtime::{
traits::{Scale, Zero},
Percent,
};
use sp_runtime::traits::{Scale, Zero};

const MAX_ED_MULTIPLE: Balance = 10_000;
const MIN_ED_MULTIPLE: Balance = 10;
Expand Down Expand Up @@ -197,7 +194,8 @@ fn random_calls<R: Rng>(
let amount = random_ed_multiple(&mut rng);
let evm_address =
if rng.gen_bool(0.5) { Some(rng.gen::<[u8; 20]>().into()) } else { None };
[(mad::Call::deposit { asset_id, amount, evm_address }, origin)].to_vec()
[(mad::Call::deposit { asset_id, amount, evm_address, lock_multiplier: None }, origin)]
.to_vec()
},
"schedule_withdraw" => {
// Schedule withdraw
Expand Down Expand Up @@ -283,70 +281,6 @@ fn random_calls<R: Rng>(
]
.to_vec()
},
"set_incentive_apy_and_cap" => {
// Set incentive APY and cap
let is_root = rng.gen_bool(0.5);
let (origin, who) = if is_root {
(RuntimeOrigin::root(), [0u8; 32].into())
} else {
random_signed_origin(&mut rng)
};
fund_account(&mut rng, &who);
let vault_id = rng.gen();
let apy = Percent::from_percent(rng.gen_range(0..100));
let cap = rng.gen_range(0..Balance::MAX);
[(mad::Call::set_incentive_apy_and_cap { vault_id, apy, cap }, origin)].to_vec()
},
"whitelist_blueprint_for_rewards" => {
// Whitelist blueprint for rewards
let is_root = rng.gen_bool(0.5);
let (origin, who) = if is_root {
(RuntimeOrigin::root(), [0u8; 32].into())
} else {
random_signed_origin(&mut rng)
};
fund_account(&mut rng, &who);
let blueprint_id = rng.gen::<u64>();
[(mad::Call::whitelist_blueprint_for_rewards { blueprint_id }, origin)].to_vec()
},
"manage_asset_in_vault" => {
// Manage asset in vault
let is_root = rng.gen_bool(0.5);
let (origin, who) = if is_root {
(RuntimeOrigin::root(), [0u8; 32].into())
} else {
random_signed_origin(&mut rng)
};
fund_account(&mut rng, &who);
let asset_id = random_asset(&mut rng);
let vault_id = rng.gen();
let action = if rng.gen() { AssetAction::Add } else { AssetAction::Remove };
[(mad::Call::manage_asset_in_vault { asset_id, vault_id, action }, origin)].to_vec()
},
"add_blueprint_id" => {
// Add blueprint ID
let is_root = rng.gen_bool(0.5);
let (origin, who) = if is_root {
(RuntimeOrigin::root(), [0u8; 32].into())
} else {
random_signed_origin(&mut rng)
};
fund_account(&mut rng, &who);
let blueprint_id = rng.gen::<u64>();
[(mad::Call::add_blueprint_id { blueprint_id }, origin)].to_vec()
},
"remove_blueprint_id" => {
// Remove blueprint ID
let is_root = rng.gen_bool(0.5);
let (origin, who) = if is_root {
(RuntimeOrigin::root(), [0u8; 32].into())
} else {
random_signed_origin(&mut rng)
};
fund_account(&mut rng, &who);
let blueprint_id = rng.gen::<u64>();
[(mad::Call::remove_blueprint_id { blueprint_id }, origin)].to_vec()
},
_ => {
unimplemented!("unknown call name: {}", op)
},
Expand Down Expand Up @@ -553,11 +487,6 @@ fn do_sanity_checks(call: mad::Call<Runtime>, origin: RuntimeOrigin, outcome: Po
mad::Call::schedule_delegator_unstake { operator, asset_id, amount } => {},
mad::Call::execute_delegator_unstake {} => {},
mad::Call::cancel_delegator_unstake { operator, asset_id, amount } => {},
mad::Call::set_incentive_apy_and_cap { vault_id, apy, cap } => {},
mad::Call::whitelist_blueprint_for_rewards { blueprint_id } => {},
mad::Call::manage_asset_in_vault { vault_id, asset_id, action } => {},
mad::Call::add_blueprint_id { blueprint_id } => {},
mad::Call::remove_blueprint_id { blueprint_id } => {},
other => unimplemented!("sanity checks for call: {other:?} not implemented"),
}
}
1 change: 0 additions & 1 deletion pallets/multi-asset-delegation/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@ pub mod delegate;
pub mod deposit;
pub mod evm;
pub mod operator;
pub mod rewards;
pub mod session_manager;
58 changes: 32 additions & 26 deletions pallets/multi-asset-delegation/src/functions/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,13 @@ impl<T: Config> Pallet<T> {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;

// Ensure enough deposited balance
let balance =
let user_deposit =
metadata.deposits.get_mut(&asset_id).ok_or(Error::<T>::InsufficientBalance)?;
ensure!(*balance >= amount, Error::<T>::InsufficientBalance);

// Reduce the balance in deposits
*balance = balance.checked_sub(&amount).ok_or(Error::<T>::InsufficientBalance)?;
if *balance == Zero::zero() {
metadata.deposits.remove(&asset_id);
}
// update the user deposit
user_deposit
.increase_delegated_amount(amount)
.map_err(|_| Error::<T>::InsufficientBalance)?;

// Check if the delegation exists and update it, otherwise create a new delegation
if let Some(delegation) = metadata
Expand Down Expand Up @@ -237,27 +235,35 @@ impl<T: Config> Pallet<T> {
ensure!(!metadata.delegator_unstake_requests.is_empty(), Error::<T>::NoBondLessRequest);

let current_round = Self::current_round();
let delay = T::DelegationBondLessDelay::get();

// Process all ready unstake requests
let mut executed_requests = Vec::new();
metadata.delegator_unstake_requests.retain(|request| {
let delay = T::DelegationBondLessDelay::get();
if current_round >= delay + request.requested_round {
// Add the amount back to the delegator's deposits
metadata
.deposits
.entry(request.asset_id)
.and_modify(|e| *e += request.amount)
.or_insert(request.amount);
executed_requests.push(request.clone());
false // Remove this request
} else {
true // Keep this request
}
});
// First, collect all ready requests and process them
let ready_requests: Vec<_> = metadata
.delegator_unstake_requests
.iter()
.filter(|request| current_round >= delay + request.requested_round)
.cloned()
.collect();

// If no requests are ready, return an error
ensure!(!ready_requests.is_empty(), Error::<T>::BondLessNotReady);

// Process each ready request
for request in ready_requests.iter() {
let deposit_record = metadata
.deposits
.get_mut(&request.asset_id)
.ok_or(Error::<T>::InsufficientBalance)?;

deposit_record
.decrease_delegated_amount(request.amount)
.map_err(|_| Error::<T>::InsufficientBalance)?;
}

// If no requests were executed, return an error
ensure!(!executed_requests.is_empty(), Error::<T>::BondLessNotReady);
// Remove the processed requests
metadata
.delegator_unstake_requests
.retain(|request| current_round < delay + request.requested_round);

Ok(())
})
Expand Down
50 changes: 30 additions & 20 deletions pallets/multi-asset-delegation/src/functions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ use crate::{types::*, Pallet};
use frame_support::{
ensure,
pallet_prelude::DispatchResult,
sp_runtime::traits::{AccountIdConversion, CheckedAdd, Zero},
sp_runtime::traits::AccountIdConversion,
traits::{fungibles::Mutate, tokens::Preservation, Get},
};
use sp_core::H160;
use tangle_primitives::services::{Asset, EvmAddressMapping};
use tangle_primitives::types::rewards::LockMultiplier;

impl<T: Config> Pallet<T> {
/// Returns the account ID of the pallet.
Expand Down Expand Up @@ -89,22 +90,27 @@ impl<T: Config> Pallet<T> {
asset_id: Asset<T::AssetId>,
amount: BalanceOf<T>,
evm_address: Option<H160>,
lock_multiplier: Option<LockMultiplier>,
) -> DispatchResult {
ensure!(amount >= T::MinDelegateAmount::get(), Error::<T>::BondTooLow);

// Transfer the amount to the pallet account
Self::handle_transfer_to_pallet(&who, asset_id, amount, evm_address)?;

let now = <frame_system::Pallet<T>>::block_number();

// Update storage
Delegators::<T>::try_mutate(&who, |maybe_metadata| -> DispatchResult {
let metadata = maybe_metadata.get_or_insert_with(Default::default);
// Handle checked addition first to avoid ? operator in closure
if let Some(existing) = metadata.deposits.get(&asset_id) {
let new_amount =
existing.checked_add(&amount).ok_or(Error::<T>::DepositOverflow)?;
metadata.deposits.insert(asset_id, new_amount);
// If there's an existing deposit, increase it
if let Some(existing) = metadata.deposits.get_mut(&asset_id) {
existing
.increase_deposited_amount(amount, lock_multiplier, now)
.map_err(|_| Error::<T>::InsufficientBalance)?;
} else {
metadata.deposits.insert(asset_id, amount);
// Create a new deposit if none exists
let new_deposit = Deposit::new(amount, lock_multiplier, now);
metadata.deposits.insert(asset_id, new_deposit);
}
Ok(())
})?;
Expand Down Expand Up @@ -132,16 +138,14 @@ impl<T: Config> Pallet<T> {
Delegators::<T>::try_mutate(&who, |maybe_metadata| {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;

let now = <frame_system::Pallet<T>>::block_number();

// Ensure there is enough deposited balance
let balance =
let deposit =
metadata.deposits.get_mut(&asset_id).ok_or(Error::<T>::InsufficientBalance)?;
ensure!(*balance >= amount, Error::<T>::InsufficientBalance);

// Reduce the balance in deposits
*balance -= amount;
if *balance == Zero::zero() {
metadata.deposits.remove(&asset_id);
}
deposit
.decrease_deposited_amount(amount, now)
.map_err(|_| Error::<T>::InsufficientBalance)?;

// Create the unstake request
let current_round = Self::current_round();
Expand Down Expand Up @@ -244,6 +248,7 @@ impl<T: Config> Pallet<T> {
) -> DispatchResult {
Delegators::<T>::try_mutate(&who, |maybe_metadata| {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;
let now = <frame_system::Pallet<T>>::block_number();

// Find and remove the matching withdraw request
let request_index = metadata
Expand All @@ -255,11 +260,16 @@ impl<T: Config> Pallet<T> {
let withdraw_request = metadata.withdraw_requests.remove(request_index);

// Add the amount back to the delegator's deposits
metadata
.deposits
.entry(asset_id)
.and_modify(|e| *e += withdraw_request.amount)
.or_insert(withdraw_request.amount);
if let Some(deposit) = metadata.deposits.get_mut(&withdraw_request.asset_id) {
deposit
.increase_deposited_amount(withdraw_request.amount, None, now)
.map_err(|_| Error::<T>::InsufficientBalance)?;
} else {
// we are only able to withdraw from existing deposits without any locks
// so when we add back, add it without any locks
let new_deposit = Deposit::new(withdraw_request.amount, None, now);
metadata.deposits.insert(withdraw_request.asset_id, new_deposit);
}

// Update the status if no more delegations exist
if metadata.delegations.is_empty() {
Expand Down
Loading

0 comments on commit 0b826fc

Please sign in to comment.