Skip to content

Commit

Permalink
WIP reward-distribution hook and percentage caculation
Browse files Browse the repository at this point in the history
  • Loading branch information
gianfra-t committed Oct 12, 2023
1 parent 06f3fa5 commit ef50a31
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 51 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

33 changes: 0 additions & 33 deletions pallets/fee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,39 +485,6 @@ impl<T: Config> Pallet<T> {
}

fn distribute(reward: &Amount<T>) -> Result<Amount<T>, DispatchError> {
//TODO commented logic will go to the reward-distribution pallet most likely

//fetch total stake (all), and calulate total usd stake across pools
//distribute the rewards into each reward pool for each collateral,
//taking into account it's value in usd

// let total_stakes = T::VaultRewards::get_total_stake_all_pools()?;
// let total_stake_in_usd = BalanceOf::<T>::default();
// for (currency_id, stake) in total_stakes.clone().into_iter() {
// let stake_in_amount = Amount::<T>::new(stake, currency_id);
// let stake_in_usd = stake_in_amount.convert_to(<T as Config>::BaseCurrency::get())?;
// total_stake_in_usd
// .checked_add(&stake_in_usd.amount())
// .ok_or(Error::<T>::Overflow)?;
// }

// let error_reward_accum = Amount::<T>::zero(reward.currency());

// for (currency_id, stake) in total_stakes.into_iter() {
// let stake_in_amount = Amount::<T>::new(stake, currency_id);
// let stake_in_usd = stake_in_amount.convert_to(<T as Config>::BaseCurrency::get())?;
// let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd);

// // multiply with floor or ceil?
// let reward_for_pool = percentage.mul_floor(reward.amount());
// let error_reward_accum = Amount::<T>::zero(reward.currency());
// if T::VaultRewards::distribute_reward(&currency_id, reward.currency(), reward_for_pool)
// .is_err()
// {
// error_reward_accum.checked_add(&reward)?;
// }
// }

//TODO INTERFACE REQUIRED
//From reward-distribution pallet we will pass the reward in wrapped token and
//it will be in charge of distributing to all pools by it's stake in usd
Expand Down
3 changes: 3 additions & 0 deletions pallets/oracle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,6 @@ impl<T: Config> Pallet<T> {
}
}

pub trait OracleApi<Balance, CurrencyId> {
fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result<Balance, DispatchError>;
}
4 changes: 4 additions & 0 deletions pallets/reward-distribution/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v
sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false }


frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false, optional = true }

pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false }

primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false }
security = { path = "../security", default-features = false }
currency = { path = "../currency", default-features = false }
pooled-rewards = { path = "../pooled-rewards", default-features = false }

[features]
default = ["std"]
Expand Down
5 changes: 5 additions & 0 deletions pallets/reward-distribution/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pub mod benchmarks {
assert_eq!(RewardDistribution::<T>::reward_per_block(), Some(new_reward_per_block));
}

#[benchmark]
fn on_initialize() {
Timestamp::<T>::set_timestamp(1000u32.into());
}

impl_benchmark_test_suite!(
RewardDistribution,
crate::mock::ExtBuilder::build(),
Expand Down
20 changes: 7 additions & 13 deletions pallets/reward-distribution/src/default_weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use core::marker::PhantomData;
/// Weight functions needed for reward_distribution.
pub trait WeightInfo {
fn set_reward_per_block() -> Weight;
fn on_initialize() -> Weight;
}

/// Weights for reward_distribution using the Substrate node and recommended hardware.
Expand All @@ -48,20 +49,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
Weight::from_parts(4_000_000, 0)
.saturating_add(T::DbWeight::get().writes(2_u64))
}
}

// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: RewardDistribution RewardsAdaptedAt (r:0 w:1)
/// Proof: RewardDistribution RewardsAdaptedAt (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
/// Storage: RewardDistribution RewardPerBlock (r:0 w:1)
/// Proof: RewardDistribution RewardPerBlock (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
fn set_reward_per_block() -> Weight {
fn on_initialize() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 3_000_000 picoseconds.
Weight::from_parts(4_000_000, 0)
.saturating_add(RocksDbWeight::get().writes(2_u64))
// Minimum execution time: 1_000_000 picoseconds.
Weight::from_parts(2_000_000, 0)
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(2))
}
}
}
19 changes: 19 additions & 0 deletions pallets/reward-distribution/src/ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#[cfg(test)]
use mocktopus::macros::mockable;

#[cfg_attr(test, mockable)]
pub(crate) mod security {
use frame_support::dispatch::DispatchResult;
use sp_runtime::DispatchError;

pub fn ensure_parachain_status_running<T: crate::Config>() -> DispatchResult {
<security::Pallet<T>>::ensure_parachain_status_running()
}

pub fn parachain_block_expired<T: crate::Config>(
opentime: T::BlockNumber,
period: T::BlockNumber,
) -> Result<bool, DispatchError> {
<security::Pallet<T>>::parachain_block_expired(opentime, period)
}
}
139 changes: 136 additions & 3 deletions pallets/reward-distribution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,26 @@ mod default_weights;

pub use default_weights::{SubstrateWeight, WeightInfo};

use crate::types::{AccountIdOf, BalanceOf};
use crate::types::{AccountIdOf, BalanceOf, DefaultVaultId};
use codec::{FullCodec, MaxEncodedLen};
use frame_support::{
dispatch::DispatchResult,
pallet_prelude::DispatchError,
traits::{Currency, Get},
transactional,
};
use pooled_rewards::RewardsApi;
use primitives::CurrencyId;
use sp_arithmetic::Perquintill;

use sp_runtime::{
traits::{AtLeast32BitUnsigned, CheckedAdd, One},
FixedPointOperand,
};
use sp_std::fmt::Debug;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

mod ext;
#[cfg(test)]
mod mock;

Expand Down Expand Up @@ -51,7 +60,31 @@ pub mod pallet {
type WeightInfo: WeightInfo;

/// The currency trait.
type Currency: Currency<AccountIdOf<Self>>;
type Currency: Currency<AccountIdOf<Self>> + Clone;

//balance
type Balance: AtLeast32BitUnsigned
+ TypeInfo
+ FixedPointOperand
+ MaybeSerializeDeserialize
+ FullCodec
+ MaxEncodedLen
+ Copy
+ Default
+ Debug
+ Clone
+ From<u64>;

type VaultRewards: pooled_rewards::RewardsApi<
Self::Currency,
DefaultVaultId<Self>,
BalanceOf<Self>,
Self::Currency,
>;

type MaxCurrencies: Get<u32>;

type OracleApi: ToUsdApi<Self::Balance, Self::Currency>;

/// Defines the interval (in number of blocks) at which the reward per block decays.
#[pallet::constant]
Expand All @@ -72,6 +105,15 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {}

#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
Self::distribute_rewards(n);
//TODO benchmark this weight properly
<T as Config>::WeightInfo::on_initialize()
}
}

/// Reward per block.
#[pallet::storage]
#[pallet::getter(fn reward_per_block)]
Expand All @@ -81,6 +123,14 @@ pub mod pallet {
#[pallet::getter(fn rewards_adapted_at)]
pub(super) type RewardsAdaptedAt<T: Config> = StorageValue<_, BlockNumberFor<T>, OptionQuery>;

#[pallet::storage]
#[pallet::getter(fn rewards_percentage)]
pub(super) type RewardsPercentage<T: Config> = StorageValue<
_,
BoundedVec<(CurrencyId, Perquintill), <T as pallet::Config>::MaxCurrencies>,
OptionQuery,
>;

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Sets the reward per block.
Expand All @@ -101,3 +151,86 @@ pub mod pallet {
}
}
}

impl<T: Config> Pallet<T> {
pub fn distribute_rewards(height: T::BlockNumber) {
//get reward per block
let reward_per_block = RewardPerBlock::<T>::get();
if reward_per_block == None {
return
}

if let Err(_) = ext::security::ensure_parachain_status_running::<T>() {
return
}

let mut reward_this_block = reward_per_block.expect("checked for none");
//update the reward per block if decay interval passed
let rewards_adapted_at = RewardsAdaptedAt::<T>::get();
if ext::security::parachain_block_expired::<T>(
rewards_adapted_at.expect("checked for none"),
T::DecayInterval::get() - T::BlockNumber::one(),
)
.unwrap()
{
let decay_rate = T::DecayRate::get();
let reward_this_block =
decay_rate.mul_floor(reward_per_block.expect("checked for none"));
RewardPerBlock::<T>::set(Some(new_reward_per_block));

RewardsAdaptedAt::<T>::set(Some(height));
}

let total_reward_stake = Self::caculate_reward_stake_percentages(reward_this_block).unwrap();


}

//fetch total stake (all), and calulate total usd stake in percentage across pools
fn caculate_reward_stake_percentages() -> Result<(), ()> {
let total_stakes = T::VaultRewards::get_total_stake_all_pools().unwrap();
let total_stake_in_usd = BalanceOf::<T>::default();
for (currency_id, stake) in total_stakes.clone().into_iter() {
let stake_in_usd = T::OracleApi::currency_to_usd(stake, currency_id).unwrap();
total_stake_in_usd.checked_add(&stake_in_usd).unwrap();
}
Ok(total_stake_in_usd)

let percentages_vec: Vec::new();
for (currency_id, stake) in total_stakes.into_iter() {
let stake_in_amount = Amount::<T>::new(stake, currency_id);
let stake_in_usd = stake_in_amount.convert_to(<T as Config>::BaseCurrency::get())?;
let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd);

percentages_vec.push((currency_id, percentage))
}

let bounded_vec = BoundedVec::try_from(percentages_vec).expect("should not be longer than max currencies");

RewardsPercentage::<T>::set(bounded_vec);
}

pub fn distribute_rewards(reward)-> Result<(),(){
// multiply with floor or ceil?

let stake_percentages = RewardsPercentage::<T>::get();

for (currency_id, stake) in stake_percentages.into_iter() {
let reward_for_pool = percentage.mul_floor(reward.amount());
let error_reward_accum = Amount::<T>::zero(reward.currency());
if T::VaultRewards::distribute_reward(&currency_id, reward.currency(), reward_for_pool)
.is_err()
{
error_reward_accum.checked_add(&reward)?;
}
}

}

}



pub trait ToUsdApi<Balance, CurrencyId> {
fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result<Balance, DispatchError>;
}
7 changes: 5 additions & 2 deletions pallets/reward-distribution/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::Config;
use frame_support::traits::Currency;
//use currency::CurrencyId;
use primitives::{CurrencyId, VaultId};

pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;

pub(crate) type BalanceOf<T> = <<T as Config>::Currency as Currency<AccountIdOf<T>>>::Balance;
pub(crate) type BalanceOf<T> = <T as Config>::Balance;

pub(crate) type DefaultVaultId<T> = VaultId<<T as frame_system::Config>::AccountId, CurrencyId>;

0 comments on commit ef50a31

Please sign in to comment.