From 6d2329ecdb9f3d2aa924b98953e2d79719ee86b4 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 19:24:03 +0100 Subject: [PATCH 01/25] add reward pallet that will store stake amount for LP providers --- code/Cargo.lock | 18 + code/parachain/frame/reward/Cargo.toml | 54 +++ code/parachain/frame/reward/src/lib.rs | 534 +++++++++++++++++++++++++ 3 files changed, 606 insertions(+) create mode 100644 code/parachain/frame/reward/Cargo.toml create mode 100644 code/parachain/frame/reward/src/lib.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index 3253779e367..b9b7b9a1c2b 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -12237,6 +12237,24 @@ dependencies = [ "quick-error 1.2.3", ] +[[package]] +name = "reward" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "log 0.4.17", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic 6.0.0", + "sp-core 7.0.0", + "sp-io 7.0.0", + "sp-runtime 7.0.0", + "sp-std 5.0.0", +] + [[package]] name = "rfc6979" version = "0.3.1" diff --git a/code/parachain/frame/reward/Cargo.toml b/code/parachain/frame/reward/Cargo.toml new file mode 100644 index 00000000000..d0211a448c6 --- /dev/null +++ b/code/parachain/frame/reward/Cargo.toml @@ -0,0 +1,54 @@ +[package] +authors = ["Composable Developers"] +description = "Provides reward mechanism for LP tokens" +edition = "2021" +homepage = "https://composable.finance" +name = "reward" +version = "1.0.0" + + +[dependencies] +log = { version = "0.4.14", default-features = false } +serde = { version = "1.0.137", default-features = false, features = ["derive"], optional = true } +codec = { default-features = false, features = [ "derive", "max-encoded-len"], package = "parity-scale-codec", version = "3.0.0" } +scale-info = { version = "2.1.1", default-features = false, features = [ "derive" ] } + +# Substrate dependencies +sp-arithmetic = { default-features = false, workspace = true } +sp-core = { default-features = false, workspace = true } +sp-io = { default-features = false, workspace = true } +sp-runtime = { default-features = false, workspace = true } +sp-std = { default-features = false, workspace = true } + +frame-support = { default-features = false, workspace = true } +frame-system = { default-features = false, workspace = true } +# frame-benchmarking = { default-features = false, workspace = true, optional = true } + + +[dev-dependencies] +pallet-timestamp = { workspace = true } +# frame-benchmarking = { default-features = false, workspace = true } + +[features] +default = ["std"] +std = [ + "log/std", + "serde", + "codec/std", + + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + + "frame-support/std", + "frame-system/std", +] + +# runtime-benchmarks = [ +# "frame-benchmarking", +# "frame-support/runtime-benchmarks", +# "frame-system/runtime-benchmarks", +# ] +# try-runtime = [ "frame-support/try-runtime" ] diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs new file mode 100644 index 00000000000..f4470fbdba5 --- /dev/null +++ b/code/parachain/frame/reward/src/lib.rs @@ -0,0 +1,534 @@ +//! # Reward Module +//! Based on the [Scalable Reward Distribution](https://solmaz.io/2019/02/24/scalable-reward-changing/) algorithm. + +// #![deny(warnings)] +#![cfg_attr(test, feature(proc_macro_hygiene))] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, EncodeLike}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::Get, +}; + +use scale_info::TypeInfo; +use sp_arithmetic::FixedPointNumber; +use sp_runtime::{ + traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, MaybeSerializeDeserialize, Saturating, Zero}, + ArithmeticError, FixedI128, FixedU128, +}; +use sp_std::{cmp::PartialOrd, collections::btree_set::BTreeSet, convert::TryInto, fmt::Debug}; + +pub(crate) type SignedFixedPoint = >::SignedFixedPoint; + +pub use pallet::*; + +pub trait BalanceToFixedPoint { + fn to_fixed(self) -> Option; +} + +impl BalanceToFixedPoint for u128 { + fn to_fixed(self) -> Option { + FixedI128::checked_from_integer( + TryInto::<::Inner>::try_into(self).ok()?, + ) + } +} + +pub trait TruncateFixedPointToInt: FixedPointNumber { + /// take a fixed point number and turns it into the truncated inner representation, + /// e.g. FixedU128(1.23) -> 1u128 + fn truncate_to_inner(&self) -> Option<::Inner>; +} + +impl TruncateFixedPointToInt for FixedI128 { + fn truncate_to_inner(&self) -> Option { + self.into_inner().checked_div(FixedI128::accuracy()) + } +} + +impl TruncateFixedPointToInt for FixedU128 { + fn truncate_to_inner(&self) -> Option<::Inner> { + self.into_inner().checked_div(FixedU128::accuracy()) + } +} +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + /// ## Configuration + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Signed fixed point type. + type SignedFixedPoint: FixedPointNumber + TruncateFixedPointToInt + Encode + EncodeLike + Decode + TypeInfo; + + /// The pool identifier type. + type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The stake identifier type. + type StakeId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The currency ID type. + type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; + + #[pallet::constant] + type GetNativeCurrencyId: Get; + + #[pallet::constant] + type GetWrappedCurrencyId: Get; + } + + // The pallet's events + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event, I: 'static = ()> { + DepositStake { + pool_id: T::PoolId, + stake_id: T::StakeId, + amount: T::SignedFixedPoint, + }, + DistributeReward { + currency_id: T::CurrencyId, + amount: T::SignedFixedPoint, + }, + WithdrawStake { + pool_id: T::PoolId, + stake_id: T::StakeId, + amount: T::SignedFixedPoint, + }, + WithdrawReward { + pool_id: T::PoolId, + stake_id: T::StakeId, + currency_id: T::CurrencyId, + amount: T::SignedFixedPoint, + }, + } + + #[pallet::error] + pub enum Error { + /// Unable to convert value. + TryIntoIntError, + /// Balance not sufficient to withdraw stake. + InsufficientFunds, + /// Cannot distribute rewards without stake. + ZeroTotalStake, + } + + #[pallet::hooks] + impl, I: 'static> Hooks for Pallet { } + + /// The total stake deposited to this reward pool. + #[pallet::storage] + #[pallet::getter(fn total_stake)] + pub type TotalStake, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::PoolId, SignedFixedPoint, ValueQuery>; + + /// The total unclaimed rewards distributed to this reward pool. + /// NOTE: this is currently only used for integration tests. + #[pallet::storage] + #[pallet::getter(fn total_rewards)] + pub type TotalRewards, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CurrencyId, SignedFixedPoint, ValueQuery>; + + /// Used to compute the rewards for a participant's stake. + #[pallet::storage] + #[pallet::getter(fn reward_per_token)] + pub type RewardPerToken, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CurrencyId, + Blake2_128Concat, + T::PoolId, + SignedFixedPoint, + ValueQuery, + >; + + /// The stake of a participant in this reward pool. + #[pallet::storage] + pub type Stake, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, (T::PoolId, T::StakeId), SignedFixedPoint, ValueQuery>; + + /// Accounts for previous changes in stake size. + #[pallet::storage] + pub type RewardTally, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CurrencyId, + Blake2_128Concat, + (T::PoolId, T::StakeId), + SignedFixedPoint, + ValueQuery, + >; + + /// Track the currencies used for rewards. + #[pallet::storage] + pub type RewardCurrencies, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::PoolId, BTreeSet, ValueQuery>; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + // The pallet's dispatchable functions. + #[pallet::call] + impl, I: 'static> Pallet {} +} + +#[macro_export] +macro_rules! checked_add_mut { + ($storage:ty, $amount:expr) => { + <$storage>::mutate(|value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $amount:expr) => { + <$storage>::mutate($currency, |value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { + <$storage>::mutate($currency, $account, |value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; +} + +macro_rules! checked_sub_mut { + ($storage:ty, $amount:expr) => { + <$storage>::mutate(|value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $amount:expr) => { + <$storage>::mutate($currency, |value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { + <$storage>::mutate($currency, $account, |value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; +} + +// "Internal" functions, callable by code. +impl, I: 'static> Pallet { + pub fn stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> SignedFixedPoint { + Stake::::get((pool_id, stake_id)) + } + + pub fn get_total_rewards( + currency_id: T::CurrencyId, + ) -> Result<::Inner, DispatchError> { + Ok(Self::total_rewards(currency_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)?) + } + + pub fn deposit_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: SignedFixedPoint, + ) -> Result<(), DispatchError> { + checked_add_mut!(Stake, (pool_id, stake_id), &amount); + checked_add_mut!(TotalStake, pool_id, &amount); + + for currency_id in RewardCurrencies::::get(pool_id) { + >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + let reward_per_token_mul_amount = + reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; + *reward_tally = reward_tally + .checked_add(&reward_per_token_mul_amount) + .ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + } + + Self::deposit_event(Event::::DepositStake { + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount, + }); + + Ok(()) + } + + pub fn distribute_reward( + pool_id: &T::PoolId, + currency_id: T::CurrencyId, + reward: SignedFixedPoint, + ) -> DispatchResult { + if reward.is_zero() { + return Ok(()); + } + let total_stake = Self::total_stake(pool_id); + ensure!(!total_stake.is_zero(), Error::::ZeroTotalStake); + + // track currency for future deposits / withdrawals + RewardCurrencies::::mutate(pool_id, |reward_currencies| { + reward_currencies.insert(currency_id); + }); + + let reward_div_total_stake = reward.checked_div(&total_stake).ok_or(ArithmeticError::Underflow)?; + checked_add_mut!(RewardPerToken, currency_id, pool_id, &reward_div_total_stake); + checked_add_mut!(TotalRewards, currency_id, &reward); + + Self::deposit_event(Event::::DistributeReward { + currency_id, + amount: reward, + }); + Ok(()) + } + + pub fn compute_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result< as FixedPointNumber>::Inner, DispatchError> { + let stake = Self::stake(pool_id, stake_id); + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + // FIXME: this can easily overflow with large numbers + let stake_mul_reward_per_token = stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?; + let reward_tally = >::get(currency_id, (pool_id, stake_id)); + // TODO: this can probably be saturated + let reward = stake_mul_reward_per_token + .checked_sub(&reward_tally) + .ok_or(ArithmeticError::Underflow)? + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)?; + Ok(reward) + } + + pub fn withdraw_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: SignedFixedPoint, + ) -> Result<(), DispatchError> { + if amount > Self::stake(pool_id, stake_id) { + return Err(Error::::InsufficientFunds.into()); + } + + checked_sub_mut!(Stake, (pool_id, stake_id), &amount); + checked_sub_mut!(TotalStake, pool_id, &amount); + + for currency_id in RewardCurrencies::::get(pool_id) { + >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + let reward_per_token_mul_amount = + reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; + + *reward_tally = reward_tally + .checked_sub(&reward_per_token_mul_amount) + .ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + } + + Self::deposit_event(Event::::WithdrawStake { + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount, + }); + Ok(()) + } + + pub fn withdraw_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result< as FixedPointNumber>::Inner, DispatchError> { + let reward = Self::compute_reward(pool_id, stake_id, currency_id)?; + let reward_as_fixed = + SignedFixedPoint::::checked_from_integer(reward).ok_or(Error::::TryIntoIntError)?; + checked_sub_mut!(TotalRewards, currency_id, &reward_as_fixed); + + let stake = Self::stake(pool_id, stake_id); + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + >::insert( + currency_id, + (pool_id, stake_id), + stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?, + ); + + Self::deposit_event(Event::::WithdrawReward { + currency_id, + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount: reward_as_fixed, + }); + Ok(reward) + } +} + +pub trait RewardsApi +where + Balance: Saturating + PartialOrd, +{ + type CurrencyId; + + /// Distribute the `amount` to all participants OR error if zero total stake. + fn distribute_reward(pool_id: &PoolId, currency_id: Self::CurrencyId, amount: Balance) -> DispatchResult; + + /// Compute the expected reward for the `stake_id`. + fn compute_reward( + pool_id: &PoolId, + stake_id: &StakeId, + currency_id: Self::CurrencyId, + ) -> Result; + + /// Withdraw all rewards from the `stake_id`. + fn withdraw_reward( + pool_id: &PoolId, + stake_id: &StakeId, + currency_id: Self::CurrencyId, + ) -> Result; + + /// Deposit stake for an account. + fn deposit_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; + + /// Withdraw stake for an account. + fn withdraw_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; + + /// Withdraw all stake for an account. + fn withdraw_all_stake(pool_id: &PoolId, stake_id: &StakeId) -> DispatchResult { + Self::withdraw_stake(pool_id, stake_id, Self::get_stake(pool_id, stake_id)?) + } + + /// Return the stake associated with the `pool_id`. + fn get_total_stake(pool_id: &PoolId) -> Result; + + /// Return the stake associated with the `stake_id`. + fn get_stake(pool_id: &PoolId, stake_id: &StakeId) -> Result; + + /// Set the stake to `amount` for `stake_id` regardless of its current stake. + fn set_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult { + let current_stake = Self::get_stake(pool_id, stake_id)?; + if current_stake < amount { + let additional_stake = amount.saturating_sub(current_stake); + Self::deposit_stake(pool_id, stake_id, additional_stake) + } else if current_stake > amount { + let surplus_stake = current_stake.saturating_sub(amount); + Self::withdraw_stake(pool_id, stake_id, surplus_stake) + } else { + Ok(()) + } + } +} + +impl RewardsApi for Pallet +where + T: Config, + I: 'static, + Balance: BalanceToFixedPoint> + Saturating + PartialOrd, + ::Inner: TryInto, +{ + type CurrencyId = T::CurrencyId; + + fn distribute_reward(pool_id: &T::PoolId, currency_id: T::CurrencyId, amount: Balance) -> DispatchResult { + Pallet::::distribute_reward( + pool_id, + currency_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } + + fn compute_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result { + Pallet::::compute_reward(pool_id, stake_id, currency_id)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn withdraw_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result { + Pallet::::withdraw_reward(pool_id, stake_id, currency_id)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn get_total_stake(pool_id: &T::PoolId) -> Result { + Pallet::::total_stake(pool_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn get_stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> Result { + Pallet::::stake(pool_id, stake_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn deposit_stake(pool_id: &T::PoolId, stake_id: &T::StakeId, amount: Balance) -> DispatchResult { + Pallet::::deposit_stake( + pool_id, + stake_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } + + fn withdraw_stake(pool_id: &T::PoolId, stake_id: &T::StakeId, amount: Balance) -> DispatchResult { + Pallet::::withdraw_stake( + pool_id, + stake_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } +} + +impl RewardsApi for () +where + Balance: Saturating + PartialOrd + Default, +{ + type CurrencyId = (); + + fn distribute_reward(_: &PoolId, _: Self::CurrencyId, _: Balance) -> DispatchResult { + Ok(()) + } + + fn compute_reward(_: &PoolId, _: &StakeId, _: Self::CurrencyId) -> Result { + Ok(Default::default()) + } + + fn withdraw_reward(_: &PoolId, _: &StakeId, _: Self::CurrencyId) -> Result { + Ok(Default::default()) + } + + fn get_total_stake(_: &PoolId) -> Result { + Ok(Default::default()) + } + + fn get_stake(_: &PoolId, _: &StakeId) -> Result { + Ok(Default::default()) + } + + fn deposit_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { + Ok(()) + } + + fn withdraw_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { + Ok(()) + } +} \ No newline at end of file From 5649915bce661259881ac31bb16ba4931b150147 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 19:42:24 +0100 Subject: [PATCH 02/25] add farming pallet --- code/Cargo.lock | 21 + code/parachain/frame/farming/Cargo.toml | 59 +++ .../frame/farming/src/default_weights.rs | 166 ++++++++ code/parachain/frame/farming/src/lib.rs | 369 ++++++++++++++++++ 4 files changed, 615 insertions(+) create mode 100644 code/parachain/frame/farming/Cargo.toml create mode 100644 code/parachain/frame/farming/src/default_weights.rs create mode 100644 code/parachain/frame/farming/src/lib.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index b9b7b9a1c2b..cf2c1fc2a05 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -3769,6 +3769,27 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "farming" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "log 0.4.17", + "orml-tokens", + "orml-traits", + "pallet-timestamp", + "parity-scale-codec", + "reward", + "scale-info", + "serde", + "sp-arithmetic 6.0.0", + "sp-core 7.0.0", + "sp-io 7.0.0", + "sp-runtime 7.0.0", + "sp-std 5.0.0", +] + [[package]] name = "fastrand" version = "1.9.0" diff --git a/code/parachain/frame/farming/Cargo.toml b/code/parachain/frame/farming/Cargo.toml new file mode 100644 index 00000000000..87580806b90 --- /dev/null +++ b/code/parachain/frame/farming/Cargo.toml @@ -0,0 +1,59 @@ +[package] +authors = ["Composable Developers"] +description = "Provides reward mechanism for LP tokens" +edition = "2021" +homepage = "https://composable.finance" +name = "farming" +version = "1.0.0" + + +[dependencies] +log = { version = "0.4.14", default-features = false } +serde = { version = "1.0.137", default-features = false, features = ["derive"], optional = true } +codec = { default-features = false, features = [ "derive", "max-encoded-len"], package = "parity-scale-codec", version = "3.0.0" } +scale-info = { version = "2.1.1", default-features = false, features = [ "derive" ] } + +# Orml dependencies +orml-tokens = { workspace = true, default-features = false } +orml-traits = { workspace = true, default-features = false } + +reward = { path = "../reward", default-features = false } +# Substrate dependencies +sp-arithmetic = { default-features = false, workspace = true } +sp-core = { default-features = false, workspace = true } +sp-io = { default-features = false, workspace = true } +sp-runtime = { default-features = false, workspace = true } +sp-std = { default-features = false, workspace = true } + +frame-support = { default-features = false, workspace = true } +frame-system = { default-features = false, workspace = true } +# frame-benchmarking = { default-features = false, workspace = true, optional = true } + + +[dev-dependencies] +pallet-timestamp = { workspace = true } +# frame-benchmarking = { default-features = false, workspace = true } + +[features] +default = ["std"] +std = [ + "log/std", + "serde", + "codec/std", + + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + + "frame-support/std", + "frame-system/std", +] + +# runtime-benchmarks = [ +# "frame-benchmarking", +# "frame-support/runtime-benchmarks", +# "frame-system/runtime-benchmarks", +# ] +# try-runtime = [ "frame-support/try-runtime" ] diff --git a/code/parachain/frame/farming/src/default_weights.rs b/code/parachain/frame/farming/src/default_weights.rs new file mode 100644 index 00000000000..a9db3c76d25 --- /dev/null +++ b/code/parachain/frame/farming/src/default_weights.rs @@ -0,0 +1,166 @@ +//! Autogenerated weights for farming +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-01-17, STEPS: `100`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/interbtc-standalone +// benchmark +// pallet +// --chain +// dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// farming +// --extrinsic +// * +// --steps +// 100 +// --repeat +// 10 +// --output +// crates/farming/src/default_weights.rs +// --template +// .deploy/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for farming. +pub trait WeightInfo { + fn on_initialize(c: u32, ) -> Weight; + fn update_reward_schedule() -> Weight; + fn remove_reward_schedule() -> Weight; + fn deposit() -> Weight; + fn withdraw() -> Weight; + fn claim() -> Weight; +} + +/// Weights for farming using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Farming RewardSchedules (r:1 w:0) + // Storage: FarmingRewards TotalStake (r:1 w:0) + fn on_initialize(c: u32, ) -> Weight { + Weight::from_ref_time(18_073_005 as u64) + // Standard Error: 183_362 + .saturating_add(Weight::from_ref_time(18_555_611 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + } + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:2 w:1) + // Storage: Farming RewardSchedules (r:1 w:1) + fn update_reward_schedule() -> Weight { + Weight::from_ref_time(105_531_000 as u64) + .saturating_add(T::DbWeight::get().reads(5 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:1 w:0) + // Storage: Farming RewardSchedules (r:0 w:1) + fn remove_reward_schedule() -> Weight { + Weight::from_ref_time(83_988_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + // Storage: Farming RewardSchedules (r:2 w:0) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: FarmingRewards Stake (r:1 w:1) + // Storage: FarmingRewards TotalStake (r:1 w:1) + // Storage: FarmingRewards RewardTally (r:2 w:2) + // Storage: FarmingRewards RewardPerToken (r:2 w:0) + fn deposit() -> Weight { + Weight::from_ref_time(108_507_000 as u64) + .saturating_add(T::DbWeight::get().reads(9 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: Tokens Accounts (r:1 w:1) + // Storage: FarmingRewards Stake (r:1 w:1) + // Storage: FarmingRewards TotalStake (r:1 w:1) + // Storage: FarmingRewards RewardTally (r:2 w:2) + // Storage: FarmingRewards RewardPerToken (r:2 w:0) + fn withdraw() -> Weight { + Weight::from_ref_time(96_703_000 as u64) + .saturating_add(T::DbWeight::get().reads(7 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } + // Storage: FarmingRewards Stake (r:1 w:0) + // Storage: FarmingRewards RewardPerToken (r:1 w:0) + // Storage: FarmingRewards RewardTally (r:1 w:1) + // Storage: FarmingRewards TotalRewards (r:1 w:1) + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:2 w:1) + fn claim() -> Weight { + Weight::from_ref_time(136_142_000 as u64) + .saturating_add(T::DbWeight::get().reads(8 as u64)) + .saturating_add(T::DbWeight::get().writes(5 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Farming RewardSchedules (r:1 w:0) + // Storage: FarmingRewards TotalStake (r:1 w:0) + fn on_initialize(c: u32, ) -> Weight { + Weight::from_ref_time(18_073_005 as u64) + // Standard Error: 183_362 + .saturating_add(Weight::from_ref_time(18_555_611 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + } + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:2 w:1) + // Storage: Farming RewardSchedules (r:1 w:1) + fn update_reward_schedule() -> Weight { + Weight::from_ref_time(105_531_000 as u64) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:1 w:0) + // Storage: Farming RewardSchedules (r:0 w:1) + fn remove_reward_schedule() -> Weight { + Weight::from_ref_time(83_988_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + // Storage: Farming RewardSchedules (r:2 w:0) + // Storage: Tokens Accounts (r:1 w:1) + // Storage: FarmingRewards Stake (r:1 w:1) + // Storage: FarmingRewards TotalStake (r:1 w:1) + // Storage: FarmingRewards RewardTally (r:2 w:2) + // Storage: FarmingRewards RewardPerToken (r:2 w:0) + fn deposit() -> Weight { + Weight::from_ref_time(108_507_000 as u64) + .saturating_add(RocksDbWeight::get().reads(9 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: Tokens Accounts (r:1 w:1) + // Storage: FarmingRewards Stake (r:1 w:1) + // Storage: FarmingRewards TotalStake (r:1 w:1) + // Storage: FarmingRewards RewardTally (r:2 w:2) + // Storage: FarmingRewards RewardPerToken (r:2 w:0) + fn withdraw() -> Weight { + Weight::from_ref_time(96_703_000 as u64) + .saturating_add(RocksDbWeight::get().reads(7 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } + // Storage: FarmingRewards Stake (r:1 w:0) + // Storage: FarmingRewards RewardPerToken (r:1 w:0) + // Storage: FarmingRewards RewardTally (r:1 w:1) + // Storage: FarmingRewards TotalRewards (r:1 w:1) + // Storage: Tokens Accounts (r:2 w:2) + // Storage: System Account (r:2 w:1) + fn claim() -> Weight { + Weight::from_ref_time(136_142_000 as u64) + .saturating_add(RocksDbWeight::get().reads(8 as u64)) + .saturating_add(RocksDbWeight::get().writes(5 as u64)) + } +} diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs new file mode 100644 index 00000000000..830e77d7b90 --- /dev/null +++ b/code/parachain/frame/farming/src/lib.rs @@ -0,0 +1,369 @@ +//! # Farming Module +//! Root can create reward schedules paying incentives periodically to users +//! staking certain tokens. +//! +//! A reward schedule consists of two items: +//! 1. The number of periods set globally as a configuration for all pools. +//! This number is ultimately measured in blocks; e.g., if a period is +//! defined as 10 blocks, then a period count of 10 means 100 blocks. +//! 2. The amount of reward tokens paid in that period. +//! +//! Users are only paid a share of the rewards in the period if they have +//! staked tokens for a reward schedule that distributed more than 0 tokens. +//! +//! The following design decisions have been made: +//! - The reward schedule is configured as a matrix such that a staked token (e.g., an AMM LP token) and an incentive +//! token (e.g., INTR or DOT) represent one reward schedule. This enables adding multiple reward currencies per staked +//! token. +//! - Rewards can be increased but not decreased unless the schedule is explicitly removed. +//! - The rewards period cannot change without a migration. +//! - Only constant rewards per period are paid. To implement more complex reward schemes, the farming pallet relies on +//! the scheduler pallet. This allows a creator to configure different constant payouts by scheduling +//! `update_reward_schedule` in the future. + +// #![deny(warnings)] +#![cfg_attr(test, feature(proc_macro_hygiene))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +mod default_weights; +pub use default_weights::WeightInfo; + + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{dispatch::DispatchResult, traits::Get, transactional, weights::Weight, PalletId, RuntimeDebug}; +use orml_traits::{MultiCurrency, MultiReservableCurrency}; +// use primitives::CurrencyId; +use reward::RewardsApi; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AccountIdConversion, AtLeast32Bit, CheckedDiv, Saturating, Zero}, + ArithmeticError, DispatchError, +}; +use sp_std::vec::Vec; + +pub use pallet::*; + +#[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct RewardSchedule { + /// Number of periods remaining + pub period_count: u32, + /// Amount of tokens to release + #[codec(compact)] + pub per_period: Balance, +} + +impl RewardSchedule { + /// Returns total amount to distribute, `None` if calculation overflows + pub fn total(&self) -> Option { + self.per_period.checked_mul(&self.period_count.into()) + } + + /// Take the next reward and decrement the period count + pub fn take(&mut self) -> Option { + if self.period_count.gt(&0) { + self.period_count.saturating_dec(); + Some(self.per_period) + } else { + None + } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + + pub(crate) type AccountIdOf = ::AccountId; + + pub(crate) type CurrencyIdOf = + <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; + + pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; + + pub(crate) type RewardScheduleOf = RewardSchedule>; + + /// ## Configuration + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The farming pallet id, used for deriving pool accounts. + #[pallet::constant] + type FarmingPalletId: Get; + + /// The treasury account id for funding pools. + #[pallet::constant] + type TreasuryAccountId: Get; + + /// The period to accrue rewards. + #[pallet::constant] + type RewardPeriod: Get; + + /// Reward pools to track stake. + type RewardPools: RewardsApi< + CurrencyIdOf, // pool id is the lp token + AccountIdOf, + BalanceOf, + CurrencyId = CurrencyIdOf, + >; + + /// Currency handler to transfer tokens. + type MultiCurrency: MultiReservableCurrency>; + // type MultiCurrency: MultiReservableCurrency, CurrencyId = CurrencyId>; + + /// Weight information for the extrinsics. //TODO + type WeightInfo: WeightInfo; + } + + // The pallet's events + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + RewardScheduleUpdated { + pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + period_count: u32, + per_period: BalanceOf, + }, + RewardDistributed { + pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + amount: BalanceOf, + }, + RewardClaimed { + account_id: AccountIdOf, + pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + amount: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + InsufficientStake, + } + + #[pallet::hooks] + impl Hooks for Pallet { + fn on_initialize(now: T::BlockNumber) -> Weight { + if now % T::RewardPeriod::get() == Zero::zero() { + let mut count: u32 = 0; + // collect first to avoid modifying in-place + let schedules = RewardSchedules::::iter().collect::>(); + for (pool_currency_id, reward_currency_id, mut reward_schedule) in schedules.into_iter() { + if let Some(amount) = reward_schedule.take() { + if let Ok(_) = Self::try_distribute_reward(pool_currency_id, reward_currency_id, amount) { + // only update the schedule if we could distribute the reward + RewardSchedules::::insert(pool_currency_id, reward_currency_id, reward_schedule); + count.saturating_inc(); + Self::deposit_event(Event::RewardDistributed { + pool_currency_id, + reward_currency_id, + amount, + }); + } + } else { + // period count is zero + RewardSchedules::::remove(pool_currency_id, reward_currency_id); + // TODO: sweep leftover rewards? + } + } + T::WeightInfo::on_initialize(count) + } else { + Weight::zero() + } + } + } + + #[pallet::storage] + #[pallet::getter(fn reward_schedules)] + pub type RewardSchedules = StorageDoubleMap< + _, + Blake2_128Concat, + CurrencyIdOf, // lp token + Blake2_128Concat, + CurrencyIdOf, // reward currency + RewardScheduleOf, + ValueQuery, + >; + + #[pallet::pallet] + pub struct Pallet(_); + + // The pallet's dispatchable functions. + #[pallet::call] + impl Pallet { + /// Create or overwrite the reward schedule, if a reward schedule + /// already exists for the rewards currency the duration is added + /// to the existing duration and the rewards per period are modified + /// s.t. that the total (old remaining + new) rewards are distributed + /// over the new total duration + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::update_reward_schedule())] + #[transactional] + pub fn update_reward_schedule( + origin: OriginFor, + mut pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + period_count: u32, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + ensure_root(origin)?; + // pool_currency_id.sort(); + + // fund the pool account from treasury + let treasury_account_id = T::TreasuryAccountId::get(); + let pool_account_id = Self::pool_account_id(&pool_currency_id); + T::MultiCurrency::transfer(reward_currency_id, &treasury_account_id, &pool_account_id, amount)?; + + RewardSchedules::::try_mutate(pool_currency_id, reward_currency_id, |reward_schedule| { + let total_period_count = reward_schedule + .period_count + .checked_add(period_count) + .ok_or(ArithmeticError::Overflow)?; + let total_free = T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id); + let total_per_period = total_free.checked_div(&total_period_count.into()).unwrap_or_default(); + + reward_schedule.period_count = total_period_count; + reward_schedule.per_period = total_per_period; + + Self::deposit_event(Event::RewardScheduleUpdated { + pool_currency_id, + reward_currency_id, + period_count: total_period_count, + per_period: total_per_period, + }); + Ok(().into()) + }) + } + + /// Explicitly remove a reward schedule and transfer any remaining + /// balance to the treasury + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::remove_reward_schedule())] + #[transactional] + pub fn remove_reward_schedule( + origin: OriginFor, + mut pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + // pool_currency_id.sort(); + + // transfer unspent rewards to treasury + let treasury_account_id = T::TreasuryAccountId::get(); + let pool_account_id = Self::pool_account_id(&pool_currency_id); + T::MultiCurrency::transfer( + reward_currency_id, + &pool_account_id, + &treasury_account_id, + T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id), + )?; + + RewardSchedules::::remove(pool_currency_id, reward_currency_id); + Self::deposit_event(Event::RewardScheduleUpdated { + pool_currency_id, + reward_currency_id, + period_count: Zero::zero(), + per_period: Zero::zero(), + }); + + Ok(().into()) + } + + /// Stake the pool tokens in the reward pool + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::deposit())] + #[transactional] + pub fn deposit(origin: OriginFor, mut pool_currency_id: CurrencyIdOf) -> DispatchResult { + let who = ensure_signed(origin)?; + // pool_currency_id.sort(); + + // reserve lp tokens to prevent spending + let amount = T::MultiCurrency::free_balance(pool_currency_id.clone(), &who); + T::MultiCurrency::reserve(pool_currency_id.clone(), &who, amount)?; + + // deposit lp tokens as stake + T::RewardPools::deposit_stake(&pool_currency_id, &who, amount) + } + + /// Unstake the pool tokens from the reward pool + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::withdraw())] + #[transactional] + pub fn withdraw( + origin: OriginFor, + mut pool_currency_id: CurrencyIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + // pool_currency_id.sort(); + + // unreserve lp tokens to allow spending + let remaining = T::MultiCurrency::unreserve(pool_currency_id.clone(), &who, amount); + ensure!(remaining.is_zero(), Error::::InsufficientStake); + + // withdraw lp tokens from stake + T::RewardPools::withdraw_stake(&pool_currency_id, &who, amount) + } + + /// Withdraw any accrued rewards from the reward pool + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::claim())] + #[transactional] + pub fn claim( + origin: OriginFor, + mut pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + // pool_currency_id.sort(); + let pool_account_id = Self::pool_account_id(&pool_currency_id); + + // get reward from staking pool + let reward = T::RewardPools::withdraw_reward(&pool_currency_id, &who, reward_currency_id)?; + // transfer from pool to user + T::MultiCurrency::transfer(reward_currency_id, &pool_account_id, &who, reward)?; + + Self::deposit_event(Event::RewardClaimed { + account_id: who, + pool_currency_id, + reward_currency_id, + amount: reward, + }); + + Ok(()) + } + } +} + +// "Internal" functions, callable by code. +impl Pallet { + pub fn pool_account_id(pool_currency_id: &CurrencyIdOf) -> T::AccountId { + T::FarmingPalletId::get().into_sub_account_truncating(pool_currency_id) + } + + pub fn total_rewards(pool_currency_id: &CurrencyIdOf, reward_currency_id: &CurrencyIdOf) -> BalanceOf { + let mut pool_currency_id = pool_currency_id.clone(); + // pool_currency_id.sort(); + RewardSchedules::::get(pool_currency_id, reward_currency_id) + .total() + .unwrap_or_default() + } + + #[transactional] + fn try_distribute_reward( + pool_currency_id: CurrencyIdOf, + reward_currency_id: CurrencyIdOf, + amount: BalanceOf, + ) -> Result<(), DispatchError> { + T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount) + } +} From a67f6d4413831420836bac2980ebe66878a643ef Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 19:48:13 +0100 Subject: [PATCH 03/25] add reward and farming pallet as dependency to picasso runtime --- code/Cargo.lock | 2 ++ code/parachain/runtime/picasso/Cargo.toml | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/code/Cargo.lock b/code/Cargo.lock index cf2c1fc2a05..4b23c3c5b77 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -9981,6 +9981,7 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-timestamp", "cumulus-primitives-utility", + "farming", "frame-benchmarking", "frame-executive", "frame-support", @@ -10037,6 +10038,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain", "primitives", + "reward", "scale-info", "smallvec 1.10.0", "sp-api", diff --git a/code/parachain/runtime/picasso/Cargo.toml b/code/parachain/runtime/picasso/Cargo.toml index 4e29756414f..ead949871a6 100644 --- a/code/parachain/runtime/picasso/Cargo.toml +++ b/code/parachain/runtime/picasso/Cargo.toml @@ -84,6 +84,8 @@ transaction-payment-rpc-runtime-api = { package = "pallet-transaction-payment-rp assets-runtime-api = { path = "../../frame/assets/runtime-api", default-features = false } crowdloan-rewards-runtime-api = { path = "../../frame/crowdloan-rewards/runtime-api", default-features = false } pablo-runtime-api = { path = "../../frame/pablo/runtime-api", default-features = false } +reward = { path = "../../frame/reward", default-features = false } +farming = { path = "../../frame/farming", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", @@ -129,4 +131,4 @@ fastnet = [] default = ["std"] local-integration-tests = [] runtime-benchmarks = ["balances/runtime-benchmarks", "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "balances/runtime-benchmarks", "timestamp/runtime-benchmarks", "collective/runtime-benchmarks", "collator-selection/runtime-benchmarks", "session-benchmarking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "indices/runtime-benchmarks", "identity/runtime-benchmarks", "multisig/runtime-benchmarks", "membership/runtime-benchmarks", "treasury/runtime-benchmarks", "scheduler/runtime-benchmarks", "collective/runtime-benchmarks", "democracy/runtime-benchmarks", "utility/runtime-benchmarks", "crowdloan-rewards/runtime-benchmarks", "currency-factory/runtime-benchmarks", "assets/runtime-benchmarks", "assets-registry/runtime-benchmarks", "vesting/runtime-benchmarks", "bonded-finance/runtime-benchmarks", "common/runtime-benchmarks", "asset-tx-payment/runtime-benchmarks", "proxy/runtime-benchmarks", "pablo/runtime-benchmarks", "oracle/runtime-benchmarks", "pallet-ibc/runtime-benchmarks"] -std = ["codec/std", "sp-api/std", "sp-std/std", "sp-core/std", "sp-runtime/std", "sp-version/std", "sp-offchain/std", "sp-session/std", "sp-block-builder/std", "sp-transaction-pool/std", "sp-inherents/std", "frame-support/std", "executive/std", "frame-system/std", "utility/std", "authorship/std", "balances/std", "randomness-collective-flip/std", "timestamp/std", "session/std", "sudo/std", "indices/std", "identity/std", "multisig/std", "call-filter/std", "orml-tokens/std", "orml-traits/std", "treasury/std", "democracy/std", "scheduler/std", "common/std", "primitives/std", "collective/std", "transaction-payment/std", "parachain-info/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", "cumulus-primitives-core/std", "cumulus-primitives-timestamp/std", "cumulus-primitives-utility/std", "collator-selection/std", "xcm/std", "xcm-builder/std", "xcm-executor/std", "aura/std", "sp-consensus-aura/std", "scale-info/std", "orml-xtokens/std", "orml-xcm-support/std", "orml-unknown-tokens/std", "composable-traits/std", "composable-support/std", "governance-registry/std", "currency-factory/std", "assets/std", "assets-transactor-router/std", "assets-registry/std", "vesting/std", "bonded-finance/std", "crowdloan-rewards/std", "preimage/std", "membership/std", "system-rpc-runtime-api/std", "transaction-payment-rpc-runtime-api/std", "assets-runtime-api/std", "crowdloan-rewards-runtime-api/std", "asset-tx-payment/std", "proxy/std", "pablo/std", "oracle/std", "pablo-runtime-api/std", "ibc/std", "pallet-ibc/std", "ibc-primitives/std", "ibc-runtime-api/std"] +std = ["codec/std", "sp-api/std", "sp-std/std", "sp-core/std", "sp-runtime/std", "sp-version/std", "sp-offchain/std", "sp-session/std", "sp-block-builder/std", "sp-transaction-pool/std", "sp-inherents/std", "frame-support/std", "executive/std", "frame-system/std", "utility/std", "authorship/std", "balances/std", "randomness-collective-flip/std", "timestamp/std", "session/std", "sudo/std", "indices/std", "identity/std", "multisig/std", "call-filter/std", "orml-tokens/std", "orml-traits/std", "treasury/std", "democracy/std", "scheduler/std", "common/std", "primitives/std", "collective/std", "transaction-payment/std", "parachain-info/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", "cumulus-primitives-core/std", "cumulus-primitives-timestamp/std", "cumulus-primitives-utility/std", "collator-selection/std", "xcm/std", "xcm-builder/std", "xcm-executor/std", "aura/std", "sp-consensus-aura/std", "scale-info/std", "orml-xtokens/std", "orml-xcm-support/std", "orml-unknown-tokens/std", "composable-traits/std", "composable-support/std", "governance-registry/std", "currency-factory/std", "assets/std", "assets-transactor-router/std", "assets-registry/std", "vesting/std", "bonded-finance/std", "crowdloan-rewards/std", "preimage/std", "membership/std", "system-rpc-runtime-api/std", "transaction-payment-rpc-runtime-api/std", "assets-runtime-api/std", "crowdloan-rewards-runtime-api/std", "asset-tx-payment/std", "proxy/std", "pablo/std", "oracle/std", "pablo-runtime-api/std", "ibc/std", "pallet-ibc/std", "ibc-primitives/std", "ibc-runtime-api/std", "reward/std", "farming/std"] From 3c177eee0d035659e2fbc2999fb2f9ff82f8a52a Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 20:12:22 +0100 Subject: [PATCH 04/25] integrate Farming and Farming Reward pallet in picasso runtime --- code/parachain/runtime/picasso/src/lib.rs | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 06700650aed..c82afb505ef 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -13,7 +13,7 @@ #![warn(clippy::unseparated_literal_suffix, clippy::disallowed_types)] #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] +#![recursion_limit = "512"] #![allow(incomplete_features)] // see other usage - #![feature(adt_const_params)] @@ -68,7 +68,7 @@ use sp_runtime::{ AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, Either, + ApplyExtrinsicResult, Either, FixedI128, }; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; @@ -282,6 +282,32 @@ impl assets::Config for Runtime { type CurrencyValidator = ValidateCurrencyId; } +type FarmingRewardsInstance = reward::Instance1; + +impl reward::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = FixedI128; + type PoolId = CurrencyId; + type StakeId = AccountId; + type CurrencyId = CurrencyId; + type GetNativeCurrencyId = NativeAssetId; + type GetWrappedCurrencyId = NativeAssetId; +} + +parameter_types! { + pub const RewardPeriod: BlockNumber = 60_000 / (12000 as BlockNumber); +} + +impl farming::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type FarmingPalletId = FarmingPalletId; + type TreasuryAccountId = TreasuryAccount; + type RewardPeriod = RewardPeriod; + type RewardPools = FarmingRewards; + type MultiCurrency = Tokens; + type WeightInfo = (); +} + parameter_types! { pub const StakeLock: BlockNumber = 50; pub const StalePrice: BlockNumber = 5; @@ -296,6 +322,7 @@ parameter_types! { pub const TwapWindow: u16 = 3; // cspell:disable-next pub const OraclePalletId: PalletId = PalletId(*b"plt_orac"); + pub const FarmingPalletId: PalletId = PalletId(*b"mod/farm"); pub const MsPerBlock: u64 = MILLISECS_PER_BLOCK as u64; } @@ -788,6 +815,8 @@ construct_runtime!( Pablo: pablo = 60, Oracle: oracle = 61, AssetsTransactorRouter: assets_transactor_router = 62, + FarmingRewards: reward:: = 63, + Farming: farming = 64, CallFilter: call_filter = 100, From 772495bf16d7e1ef0500357e299089968df5e9eb Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 20:13:49 +0100 Subject: [PATCH 05/25] use AssetsTransactorRouter for MultiCurrency associated type in farming --- code/parachain/runtime/picasso/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index c82afb505ef..10dc664c645 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -304,7 +304,7 @@ impl farming::Config for Runtime { type TreasuryAccountId = TreasuryAccount; type RewardPeriod = RewardPeriod; type RewardPools = FarmingRewards; - type MultiCurrency = Tokens; + type MultiCurrency = AssetsTransactorRouter; type WeightInfo = (); } From d6e8e465f03dfbce4fd23be61e3daf7ff5b90dac Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 20:22:40 +0100 Subject: [PATCH 06/25] reconfigure farming pallet for picasso. use separate account for farming --- code/parachain/runtime/picasso/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 10dc664c645..9b436355ac3 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -296,12 +296,14 @@ impl reward::Config for Runtime { parameter_types! { pub const RewardPeriod: BlockNumber = 60_000 / (12000 as BlockNumber); + pub const FarmingPalletId: PalletId = PalletId(*b"mod/farm"); + pub FarmingAccount: AccountId = FarmingPalletId::get().into_account_truncating(); } impl farming::Config for Runtime { type RuntimeEvent = RuntimeEvent; type FarmingPalletId = FarmingPalletId; - type TreasuryAccountId = TreasuryAccount; + type TreasuryAccountId = FarmingAccount; type RewardPeriod = RewardPeriod; type RewardPools = FarmingRewards; type MultiCurrency = AssetsTransactorRouter; @@ -322,7 +324,6 @@ parameter_types! { pub const TwapWindow: u16 = 3; // cspell:disable-next pub const OraclePalletId: PalletId = PalletId(*b"plt_orac"); - pub const FarmingPalletId: PalletId = PalletId(*b"mod/farm"); pub const MsPerBlock: u64 = MILLISECS_PER_BLOCK as u64; } From 1717978933c0aebe764d025d62f11a90fd75d0eb Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 21:20:36 +0100 Subject: [PATCH 07/25] add mock runtime and unit tests for farming pallet. --- code/Cargo.lock | 1 + code/parachain/frame/farming/Cargo.toml | 2 + .../frame/farming/src/benchmarking.rs | 123 +++++++++ code/parachain/frame/farming/src/lib.rs | 6 + code/parachain/frame/farming/src/mock.rs | 150 +++++++++++ code/parachain/frame/farming/src/tests.rs | 254 ++++++++++++++++++ 6 files changed, 536 insertions(+) create mode 100644 code/parachain/frame/farming/src/benchmarking.rs create mode 100644 code/parachain/frame/farming/src/mock.rs create mode 100644 code/parachain/frame/farming/src/tests.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index 4b23c3c5b77..7fe58cc41de 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -3778,6 +3778,7 @@ dependencies = [ "log 0.4.17", "orml-tokens", "orml-traits", + "pallet-balances", "pallet-timestamp", "parity-scale-codec", "reward", diff --git a/code/parachain/frame/farming/Cargo.toml b/code/parachain/frame/farming/Cargo.toml index 87580806b90..fcf543e7fab 100644 --- a/code/parachain/frame/farming/Cargo.toml +++ b/code/parachain/frame/farming/Cargo.toml @@ -32,6 +32,8 @@ frame-system = { default-features = false, workspace = true } [dev-dependencies] pallet-timestamp = { workspace = true } +# primitives = { path = "../../runtime/primitives", default-features = false } +pallet-balances = { workspace = true, default-features = false } # frame-benchmarking = { default-features = false, workspace = true } [features] diff --git a/code/parachain/frame/farming/src/benchmarking.rs b/code/parachain/frame/farming/src/benchmarking.rs new file mode 100644 index 00000000000..4091ee61fea --- /dev/null +++ b/code/parachain/frame/farming/src/benchmarking.rs @@ -0,0 +1,123 @@ +use super::*; +use crate::CurrencyId::Token; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_support::{assert_ok, traits::Hooks}; +use frame_system::RawOrigin; +use primitives::*; +use sp_std::vec; + +// Pallets +use crate::Pallet as Farming; +use frame_system::Pallet as System; + +fn default_reward_schedule(reward_currency_id: CurrencyId) -> RewardScheduleOf { + let reward_schedule = RewardSchedule { + period_count: 100u32, + per_period: 1000u32.into(), + }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(T::MultiCurrency::deposit( + reward_currency_id, + &T::TreasuryAccountId::get(), + total_amount, + )); + + reward_schedule +} + +fn create_reward_schedule(pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) { + let reward_schedule = default_reward_schedule::(reward_currency_id); + + assert_ok!(Farming::::update_reward_schedule( + RawOrigin::Root.into(), + pool_currency_id, + reward_currency_id, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); +} + +fn create_default_reward_schedule() -> (CurrencyId, CurrencyId) { + let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); + let reward_currency_id = CurrencyId::Token(INTR); + create_reward_schedule::(pool_currency_id, reward_currency_id); + (pool_currency_id, reward_currency_id) +} + +fn deposit_lp_tokens(pool_currency_id: CurrencyId, account_id: &T::AccountId, amount: BalanceOf) { + assert_ok!(T::MultiCurrency::deposit(pool_currency_id, account_id, amount)); + assert_ok!(Farming::::deposit( + RawOrigin::Signed(account_id.clone()).into(), + pool_currency_id, + )); +} + +pub fn get_benchmarking_currency_ids() -> Vec<(CurrencyId, CurrencyId)> { + vec![ + (Token(DOT), Token(INTR)), + (Token(KSM), Token(KINT)), + (Token(DOT), Token(IBTC)), + (Token(KSM), Token(KBTC)), + ] +} + +benchmarks! { + on_initialize { + let c in 0 .. get_benchmarking_currency_ids().len() as u32; + let currency_ids = get_benchmarking_currency_ids(); + let block_number = T::RewardPeriod::get(); + + for i in 0 .. c { + let (pool_currency_id, reward_currency_id) = currency_ids[i as usize]; + create_reward_schedule::(pool_currency_id, reward_currency_id); + } + + Farming::::on_initialize(1u32.into()); + System::::set_block_number(block_number); + }: { + Farming::::on_initialize(System::::block_number()); + } + + update_reward_schedule { + let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); + let reward_currency_id = CurrencyId::Token(INTR); + let reward_schedule = default_reward_schedule::(reward_currency_id); + + }: _(RawOrigin::Root, pool_currency_id, reward_currency_id, reward_schedule.period_count, reward_schedule.total().unwrap()) + + remove_reward_schedule { + let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); + + }: _(RawOrigin::Root, pool_currency_id, reward_currency_id) + + deposit { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, _) = create_default_reward_schedule::(); + assert_ok!(T::MultiCurrency::deposit( + pool_currency_id, + &origin, + 100u32.into(), + )); + + }: _(RawOrigin::Signed(origin), pool_currency_id) + + withdraw { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, _) = create_default_reward_schedule::(); + let amount = 100u32.into(); + deposit_lp_tokens::(pool_currency_id, &origin, amount); + + }: _(RawOrigin::Signed(origin), pool_currency_id, amount) + + claim { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); + let amount = 100u32.into(); + deposit_lp_tokens::(pool_currency_id, &origin, amount); + assert_ok!(T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount)); + + }: _(RawOrigin::Signed(origin), pool_currency_id, reward_currency_id) +} + +impl_benchmark_test_suite!(Farming, crate::mock::ExtBuilder::build(), crate::mock::Test); diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 830e77d7b90..bd82536cff6 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -31,6 +31,12 @@ mod benchmarking; mod default_weights; pub use default_weights::WeightInfo; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::DispatchResult, traits::Get, transactional, weights::Weight, PalletId, RuntimeDebug}; diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs new file mode 100644 index 00000000000..1bcf4a74ac8 --- /dev/null +++ b/code/parachain/frame/farming/src/mock.rs @@ -0,0 +1,150 @@ +use crate::{self as farming, Config, Error}; +use frame_support::{ + parameter_types, + traits::{ConstU32, Everything}, + PalletId, +}; +use orml_traits::parameter_type_with_key; +// pub use primitives::currency::CurrencyId; +use sp_arithmetic::FixedI128; +use sp_core::H256; +use sp_runtime::{ + generic::Header as GenericHeader, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, +}; + +type Header = GenericHeader; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Tokens: orml_tokens::{Pallet, Storage, /*Config,*/ Event}, + Rewards: reward::{Pallet, Call, Storage, Event}, + Farming: farming::{Pallet, Call, Storage, Event}, + } +); + +pub type CurrencyId = u128; + + +pub type AccountId = u64; +pub type Balance = u128; +pub type BlockNumber = u128; +pub type Index = u64; +pub type SignedFixedPoint = FixedI128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = Index; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = 1; + pub const GetRelayChainCurrencyId: CurrencyId = 2; + pub const GetWrappedCurrencyId: CurrencyId = 3; + pub const MaxLocks: u32 = 50; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + 0 + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = i128; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = Everything; + type MaxReserves = ConstU32<0>; // we don't use named reserves + type ReserveIdentifier = (); // we don't use named reserves +} + +impl reward::Config for Test { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type StakeId = AccountId; + type CurrencyId = CurrencyId; + type GetNativeCurrencyId = GetNativeCurrencyId; + type GetWrappedCurrencyId = GetWrappedCurrencyId; +} + +parameter_types! { + pub const FarmingPalletId: PalletId = PalletId(*b"farmings"); + pub TreasuryAccountId: AccountId = PalletId(*b"treasury").into_account_truncating(); + pub const RewardPeriod: BlockNumber = 10; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type FarmingPalletId = FarmingPalletId; + type TreasuryAccountId = TreasuryAccountId; + type RewardPeriod = RewardPeriod; + type RewardPools = Rewards; + type MultiCurrency = Tokens; + type WeightInfo = (); +} + +pub type TestEvent = RuntimeEvent; +pub type TestError = Error; + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + storage.into() + } +} + +pub fn run_test(test: T) +where + T: FnOnce(), +{ + ExtBuilder::build().execute_with(|| { + System::set_block_number(1); + test(); + }); +} diff --git a/code/parachain/frame/farming/src/tests.rs b/code/parachain/frame/farming/src/tests.rs new file mode 100644 index 00000000000..2ffe55773c1 --- /dev/null +++ b/code/parachain/frame/farming/src/tests.rs @@ -0,0 +1,254 @@ +use super::*; +use crate::mock::*; +use frame_support::{assert_err, assert_ok, traits::Hooks}; +use orml_traits::MultiCurrency; + +type Event = crate::Event; + +macro_rules! assert_emitted { + ($event:expr) => { + let test_event = TestEvent::Farming($event); + assert!(System::events().iter().any(|a| a.event == test_event)); + }; +} + +// use primitives::CurrencyId; +use crate::mock::CurrencyId; + +const POOL_CURRENCY_ID: CurrencyId = 1000; +const REWARD_CURRENCY_ID: CurrencyId = 1; + +#[test] +fn should_create_and_remove_reward_schedule() { + run_test(|| { + let reward_schedule = RewardSchedule { + period_count: 100, + per_period: 1000, + }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + // creating a reward pool should transfer from treasury + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // check pool balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + total_amount + ); + + // deleting a reward pool should transfer back to treasury + assert_ok!(Farming::remove_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + )); + + // check treasury balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &TreasuryAccountId::get()), + total_amount + ); + + assert_emitted!(Event::RewardScheduleUpdated { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + period_count: 0, + per_period: 0, + }); + }) +} + +#[test] +fn should_overwrite_existing_schedule() { + run_test(|| { + let reward_schedule_1 = RewardSchedule { + period_count: 200, + per_period: 20, + }; + let reward_schedule_2 = RewardSchedule { + period_count: 100, + per_period: 10, + }; + let total_amount = reward_schedule_1.total().unwrap() + reward_schedule_2.total().unwrap(); + let total_period_count = reward_schedule_1.period_count + reward_schedule_2.period_count; + let total_reward_per_period = total_amount / total_period_count as u128; + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + // create first reward schedule + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule_1.period_count, + reward_schedule_1.total().unwrap(), + )); + + // check pool balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + reward_schedule_1.total().unwrap(), + ); + + // overwrite second reward schedule + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule_2.period_count, + reward_schedule_2.total().unwrap(), + )); + + // check pool balance now includes both + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + total_amount, + ); + + assert_emitted!(Event::RewardScheduleUpdated { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + period_count: total_period_count, + per_period: total_reward_per_period, + }); + }) +} + +fn mint_and_deposit(account_id: AccountId, amount: Balance) { + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + account_id, + POOL_CURRENCY_ID, + amount, + 0 + )); + + assert_ok!(Farming::deposit(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID,)); +} + +#[test] +fn should_deposit_and_withdraw_stake() { + run_test(|| { + let pool_tokens = 1000; + let account_id = 0; + + let reward_schedule = RewardSchedule { + period_count: 100, + per_period: 1000, + }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // mint and deposit stake + mint_and_deposit(account_id, pool_tokens); + + // can't withdraw more stake than reserved + let withdraw_amount = pool_tokens * 2; + assert_err!( + Farming::withdraw(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID, withdraw_amount), + TestError::InsufficientStake + ); + + // only withdraw half of deposit + let withdraw_amount = pool_tokens / 2; + assert_ok!(Farming::withdraw( + RuntimeOrigin::signed(account_id), + POOL_CURRENCY_ID, + withdraw_amount + )); + assert_eq!(Tokens::free_balance(POOL_CURRENCY_ID, &account_id), withdraw_amount); + }) +} + +#[test] +fn should_deposit_stake_and_claim_reward() { + run_test(|| { + let pool_tokens = 1000; + let account_id = 0; + + // setup basic reward schedule + let reward_schedule = RewardSchedule { + period_count: 100, + per_period: 1000, + }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // mint and deposit stake + mint_and_deposit(account_id, pool_tokens); + + // check that we distribute per period + Farming::on_initialize(10); + assert_emitted!(Event::RewardDistributed { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + amount: reward_schedule.per_period, + }); + assert_eq!( + RewardSchedules::::get(POOL_CURRENCY_ID, REWARD_CURRENCY_ID).period_count, + reward_schedule.period_count - 1 + ); + + // withdraw reward + assert_ok!(Farming::claim( + RuntimeOrigin::signed(account_id), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + )); + // only one account with stake so they get all rewards + assert_eq!( + Tokens::free_balance(REWARD_CURRENCY_ID, &account_id), + reward_schedule.per_period + ); + }) +} From 9511318902a4bc42a24fb6356948e2264805e3e5 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 26 Apr 2023 21:30:14 +0100 Subject: [PATCH 08/25] add mock runtime and unit tests for reward pallet --- code/Cargo.lock | 1 + code/parachain/frame/reward/Cargo.toml | 1 + code/parachain/frame/reward/src/lib.rs | 6 + code/parachain/frame/reward/src/mock.rs | 102 +++++++++++++ code/parachain/frame/reward/src/tests.rs | 183 +++++++++++++++++++++++ 5 files changed, 293 insertions(+) create mode 100644 code/parachain/frame/reward/src/mock.rs create mode 100644 code/parachain/frame/reward/src/tests.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index 7fe58cc41de..be29aa36a5e 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -12270,6 +12270,7 @@ dependencies = [ "log 0.4.17", "pallet-timestamp", "parity-scale-codec", + "rand 0.8.5", "scale-info", "serde", "sp-arithmetic 6.0.0", diff --git a/code/parachain/frame/reward/Cargo.toml b/code/parachain/frame/reward/Cargo.toml index d0211a448c6..a604376838f 100644 --- a/code/parachain/frame/reward/Cargo.toml +++ b/code/parachain/frame/reward/Cargo.toml @@ -27,6 +27,7 @@ frame-system = { default-features = false, workspace = true } [dev-dependencies] pallet-timestamp = { workspace = true } +rand = "0.8.3" # frame-benchmarking = { default-features = false, workspace = true } [features] diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index f4470fbdba5..9a084699646 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -5,6 +5,12 @@ #![cfg_attr(test, feature(proc_macro_hygiene))] #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + use codec::{Decode, Encode, EncodeLike}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, diff --git a/code/parachain/frame/reward/src/mock.rs b/code/parachain/frame/reward/src/mock.rs new file mode 100644 index 00000000000..beb39980950 --- /dev/null +++ b/code/parachain/frame/reward/src/mock.rs @@ -0,0 +1,102 @@ +use crate as reward; +use crate::{Config, Error}; +use frame_support::{parameter_types, traits::Everything}; +use sp_arithmetic::FixedI128; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Reward: reward::{Pallet, Call, Storage, Event}, + } +); + +pub type CurrencyId = u128; +pub type AccountId = u64; +pub type BlockNumber = u64; +pub type Index = u64; +pub type SignedFixedPoint = FixedI128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = Index; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = 0; + pub const GetWrappedCurrencyId: CurrencyId = 3; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = (); + type StakeId = AccountId; + type CurrencyId = CurrencyId; + type GetNativeCurrencyId = GetNativeCurrencyId; + type GetWrappedCurrencyId = GetWrappedCurrencyId; +} + +pub type TestError = Error; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; + +pub struct ExtBuilder; + +impl ExtBuilder { + pub fn build() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + storage.into() + } +} + +pub fn run_test(test: T) +where + T: FnOnce(), +{ + ExtBuilder::build().execute_with(|| { + System::set_block_number(1); + test(); + }); +} diff --git a/code/parachain/frame/reward/src/tests.rs b/code/parachain/frame/reward/src/tests.rs new file mode 100644 index 00000000000..39bfa18f402 --- /dev/null +++ b/code/parachain/frame/reward/src/tests.rs @@ -0,0 +1,183 @@ +/// Tests for Reward +use crate::mock::*; +use frame_support::{assert_err, assert_ok}; +use rand::Rng; + +// type Event = crate::Event; + +use crate::mock::CurrencyId; + +const PICA : CurrencyId = 1; +const LP : CurrencyId = 10000; +const KSM : CurrencyId = 4; +const USDT : CurrencyId = 140; + +macro_rules! fixed { + ($amount:expr) => { + sp_arithmetic::FixedI128::from($amount) + }; +} + +#[test] +#[cfg_attr(rustfmt, rustfmt_skip)] +fn reproduce_live_state() { + // This function is most useful for debugging. Keeping this test here for convenience + // and to function as an additional regression test + run_test(|| { + let f = |x: i128| SignedFixedPoint::from_inner(x); + let currency = PICA; + + // state for a3eFe9M2HbAgrQrShEDH2CEvXACtzLhSf4JGkwuT9SQ1EV4ti at block 0xb47ed0e773e25c81da2cc606495ab6f716c3c2024f9beb361605860912fee652 + crate::RewardPerToken::::insert(currency, (), f(1_699_249_738_518_636_122_154_288_694)); + crate::RewardTally::::insert(currency, ((), ALICE), f(164_605_943_476_265_834_062_592_062_507_811_208)); + crate::Stake::::insert(((), ALICE), f(97_679_889_000_000_000_000_000_000)); + crate::TotalRewards::::insert(currency, f(8_763_982_459_262_268_000_000_000_000_000_000)); + crate::TotalStake::::insert((), f(2_253_803_217_000_000_000_000_000_000)); + + assert_ok!(Reward::compute_reward(&(), &ALICE, currency), 1376582365513566); + }) +} + +#[test] +fn should_distribute_rewards_equally() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(100))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 50); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 50); + }) +} + +#[test] +fn should_distribute_uneven_rewards_equally() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(451))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 225); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 225); + }) +} + +#[test] +fn should_not_update_previous_rewards() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(40))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); + + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(20))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 0); + }) +} + +#[test] +fn should_withdraw_reward() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(45))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(55))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2344))); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); + assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), 1054); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); + }) +} + +#[test] +fn should_withdraw_stake() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(1312))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(4242))); + // rounding in `CheckedDiv` loses some precision + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); + assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(1312))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); + }) +} + +#[test] +fn should_not_withdraw_stake_if_balance_insufficient() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 2000); + assert_err!( + Reward::withdraw_stake(&(), &ALICE, fixed!(200)), + TestError::InsufficientFunds + ); + }) +} + +#[test] +fn should_deposit_stake() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); + assert_eq!(Reward::stake(&(), &ALICE), fixed!(50)); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 500); + }) +} + +#[test] +fn should_not_distribute_rewards_without_stake() { + run_test(|| { + assert_err!( + Reward::distribute_reward(&(), LP, fixed!(1000)), + TestError::ZeroTotalStake + ); + assert_eq!(Reward::total_rewards(LP), fixed!(0)); + }) +} + +#[test] +fn should_distribute_with_many_rewards() { + // test that reward tally doesn't overflow + run_test(|| { + let mut rng = rand::thread_rng(); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(9230404))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(234234444))); + for _ in 0..30 { + // NOTE: this will overflow compute_reward with > u32 + assert_ok!(Reward::distribute_reward( + &(), + LP, + fixed!(rng.gen::() as i128) + )); + } + let alice_reward = Reward::compute_reward(&(), &ALICE, LP).unwrap(); + assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), alice_reward); + let bob_reward = Reward::compute_reward(&(), &BOB, LP).unwrap(); + assert_ok!(Reward::withdraw_reward(&(), &BOB, LP), bob_reward); + }) +} + +macro_rules! assert_approx_eq { + ($left:expr, $right:expr, $delta:expr) => { + assert!(if $left > $right { $left - $right } else { $right - $left } <= $delta) + }; +} + +#[test] +fn should_distribute_with_different_rewards() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), PICA, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), KSM, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), USDT, fixed!(1000))); + + assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(300))); + + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, LP).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, PICA).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, KSM).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, USDT).unwrap(), 1000, 1); + }) +} From 0c7cc6fdb25c34df508ddd74166fc17be8de2ca8 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 16:49:41 +0100 Subject: [PATCH 09/25] refactor reward pallet and remove unused constant from pallet config - update picasso runtime - refactor unit tests - update mock runtime for farming and reward pallet --- code/parachain/frame/farming/src/lib.rs | 12 ++++++------ code/parachain/frame/farming/src/mock.rs | 10 +++++----- code/parachain/frame/reward/src/lib.rs | 8 ++++---- code/parachain/frame/reward/src/mock.rs | 4 ++-- code/parachain/runtime/picasso/src/lib.rs | 2 -- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index bd82536cff6..79d57153ac8 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -216,7 +216,7 @@ pub mod pallet { #[transactional] pub fn update_reward_schedule( origin: OriginFor, - mut pool_currency_id: CurrencyIdOf, + pool_currency_id: CurrencyIdOf, reward_currency_id: CurrencyIdOf, period_count: u32, #[pallet::compact] amount: BalanceOf, @@ -257,7 +257,7 @@ pub mod pallet { #[transactional] pub fn remove_reward_schedule( origin: OriginFor, - mut pool_currency_id: CurrencyIdOf, + pool_currency_id: CurrencyIdOf, reward_currency_id: CurrencyIdOf, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; @@ -288,7 +288,7 @@ pub mod pallet { #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::deposit())] #[transactional] - pub fn deposit(origin: OriginFor, mut pool_currency_id: CurrencyIdOf) -> DispatchResult { + pub fn deposit(origin: OriginFor, pool_currency_id: CurrencyIdOf) -> DispatchResult { let who = ensure_signed(origin)?; // pool_currency_id.sort(); @@ -306,7 +306,7 @@ pub mod pallet { #[transactional] pub fn withdraw( origin: OriginFor, - mut pool_currency_id: CurrencyIdOf, + pool_currency_id: CurrencyIdOf, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -326,7 +326,7 @@ pub mod pallet { #[transactional] pub fn claim( origin: OriginFor, - mut pool_currency_id: CurrencyIdOf, + pool_currency_id: CurrencyIdOf, reward_currency_id: CurrencyIdOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -357,7 +357,7 @@ impl Pallet { } pub fn total_rewards(pool_currency_id: &CurrencyIdOf, reward_currency_id: &CurrencyIdOf) -> BalanceOf { - let mut pool_currency_id = pool_currency_id.clone(); + let pool_currency_id = pool_currency_id.clone(); // pool_currency_id.sort(); RewardSchedules::::get(pool_currency_id, reward_currency_id) .total() diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs index 1bcf4a74ac8..79e4fc10847 100644 --- a/code/parachain/frame/farming/src/mock.rs +++ b/code/parachain/frame/farming/src/mock.rs @@ -74,9 +74,9 @@ impl frame_system::Config for Test { } parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = 1; - pub const GetRelayChainCurrencyId: CurrencyId = 2; - pub const GetWrappedCurrencyId: CurrencyId = 3; + // pub const GetNativeCurrencyId: CurrencyId = 1; + // pub const GetRelayChainCurrencyId: CurrencyId = 2; + // pub const GetWrappedCurrencyId: CurrencyId = 3; pub const MaxLocks: u32 = 50; } @@ -106,8 +106,8 @@ impl reward::Config for Test { type PoolId = CurrencyId; type StakeId = AccountId; type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; - type GetWrappedCurrencyId = GetWrappedCurrencyId; + // type GetNativeCurrencyId = GetNativeCurrencyId; + // type GetWrappedCurrencyId = GetWrappedCurrencyId; } parameter_types! { diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index 9a084699646..35bc895b640 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -83,11 +83,11 @@ pub mod pallet { /// The currency ID type. type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; - #[pallet::constant] - type GetNativeCurrencyId: Get; + // #[pallet::constant] + // type GetNativeCurrencyId: Get; - #[pallet::constant] - type GetWrappedCurrencyId: Get; + // #[pallet::constant] + // type GetWrappedCurrencyId: Get; } // The pallet's events diff --git a/code/parachain/frame/reward/src/mock.rs b/code/parachain/frame/reward/src/mock.rs index beb39980950..5a8f33d93a4 100644 --- a/code/parachain/frame/reward/src/mock.rs +++ b/code/parachain/frame/reward/src/mock.rs @@ -72,8 +72,8 @@ impl Config for Test { type PoolId = (); type StakeId = AccountId; type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; - type GetWrappedCurrencyId = GetWrappedCurrencyId; + // type GetNativeCurrencyId = GetNativeCurrencyId; + // type GetWrappedCurrencyId = GetWrappedCurrencyId; } pub type TestError = Error; diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 06c452c3806..9a2b1b53e1e 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -290,8 +290,6 @@ impl reward::Config for Runtime { type PoolId = CurrencyId; type StakeId = AccountId; type CurrencyId = CurrencyId; - type GetNativeCurrencyId = NativeAssetId; - type GetWrappedCurrencyId = NativeAssetId; } parameter_types! { From bcc0454b3509aed4b02a14a8796ac74a5bc8f57f Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 18:33:35 +0100 Subject: [PATCH 10/25] add runtime api for reward pallet --- code/Cargo.lock | 11 ++++ .../frame/reward/rpc/runtime-api/Cargo.toml | 27 ++++++++ .../frame/reward/rpc/runtime-api/src/lib.rs | 64 +++++++++++++++++++ code/parachain/runtime/picasso/Cargo.toml | 4 +- 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 code/parachain/frame/reward/rpc/runtime-api/Cargo.toml create mode 100644 code/parachain/frame/reward/rpc/runtime-api/src/lib.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index b5c11ab05dc..0fa046e48bd 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -10040,6 +10040,7 @@ dependencies = [ "polkadot-parachain", "primitives", "reward", + "reward-rpc-runtime-api", "scale-info", "smallvec 1.10.0", "sp-api", @@ -12280,6 +12281,16 @@ dependencies = [ "sp-std 5.0.0", ] +[[package]] +name = "reward-rpc-runtime-api" +version = "0.3.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "serde", + "sp-api", +] + [[package]] name = "rfc6979" version = "0.3.1" diff --git a/code/parachain/frame/reward/rpc/runtime-api/Cargo.toml b/code/parachain/frame/reward/rpc/runtime-api/Cargo.toml new file mode 100644 index 00000000000..39931b789fb --- /dev/null +++ b/code/parachain/frame/reward/rpc/runtime-api/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = ["Composable Developers"] +edition = "2021" +name = "reward-rpc-runtime-api" +version = '0.3.0' + +[dependencies] +codec = { default-features = false, features = [ + "derive", "max-encoded-len" +], package = "parity-scale-codec", version = "3.0.0" } +sp-api = { default-features = false, workspace = true } +frame-support = { default-features = false, workspace = true } +serde = { version = '1.0.136', optional = true } + +# [dependencies.oracle-rpc-runtime-api] +# default-features = false +# path = '../../../oracle/rpc/runtime-api' + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "sp-api/std", + "serde" + # "oracle-rpc-runtime-api/std", +] diff --git a/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs new file mode 100644 index 00000000000..0d55c343c8d --- /dev/null +++ b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs @@ -0,0 +1,64 @@ +//! Runtime API definition for the Reward Module. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +use frame_support::dispatch::DispatchError; + +use codec::{Decode, Encode}; +#[cfg(feature = "std")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Eq, PartialEq, Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +/// a wrapper around a balance, used in RPC to workaround a bug where using u128 +/// in runtime-apis fails. See +pub struct BalanceWrapper { + #[cfg_attr(feature = "std", serde(bound(serialize = "T: std::fmt::Display")))] + #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] + #[cfg_attr(feature = "std", serde(bound(deserialize = "T: std::str::FromStr")))] + #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] + pub amount: T, +} + +#[cfg(feature = "std")] +fn serialize_as_string(t: &T, serializer: S) -> Result { + serializer.serialize_str(&t.to_string()) +} + +#[cfg(feature = "std")] +fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + s.parse::() + .map_err(|_| serde::de::Error::custom("Parse from string failed")) +} + +sp_api::decl_runtime_apis! { + pub trait RewardApi where + AccountId: Codec, + VaultId: Codec, + CurrencyId: Codec, + Balance: Codec, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, + { + /// Calculate the number of escrow rewards accrued + fn compute_escrow_reward(account_id: AccountId, currency_id: CurrencyId) -> Result, DispatchError>; + + /// Calculate the number of farming rewards accrued + fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; + + /// Calculate the number of vault rewards accrued + fn compute_vault_reward(vault_id: VaultId, currency_id: CurrencyId) -> Result, DispatchError>; + + /// Estimate staking reward rate for a one year period + fn estimate_escrow_reward_rate(account_id: AccountId, amount: Option, lock_time: Option) -> Result; + + /// Estimate farming rewards for remaining incentives + fn estimate_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; + + /// Estimate vault reward rate for a one year period + fn estimate_vault_reward_rate(vault_id: VaultId) -> Result; + } +} diff --git a/code/parachain/runtime/picasso/Cargo.toml b/code/parachain/runtime/picasso/Cargo.toml index ead949871a6..865460dc5e3 100644 --- a/code/parachain/runtime/picasso/Cargo.toml +++ b/code/parachain/runtime/picasso/Cargo.toml @@ -87,6 +87,8 @@ pablo-runtime-api = { path = "../../frame/pablo/runtime-api", default-features = reward = { path = "../../frame/reward", default-features = false } farming = { path = "../../frame/farming", default-features = false } +reward-rpc-runtime-api = { path = "../../frame/reward/rpc/runtime-api", default-features = false } + codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } @@ -131,4 +133,4 @@ fastnet = [] default = ["std"] local-integration-tests = [] runtime-benchmarks = ["balances/runtime-benchmarks", "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "balances/runtime-benchmarks", "timestamp/runtime-benchmarks", "collective/runtime-benchmarks", "collator-selection/runtime-benchmarks", "session-benchmarking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "indices/runtime-benchmarks", "identity/runtime-benchmarks", "multisig/runtime-benchmarks", "membership/runtime-benchmarks", "treasury/runtime-benchmarks", "scheduler/runtime-benchmarks", "collective/runtime-benchmarks", "democracy/runtime-benchmarks", "utility/runtime-benchmarks", "crowdloan-rewards/runtime-benchmarks", "currency-factory/runtime-benchmarks", "assets/runtime-benchmarks", "assets-registry/runtime-benchmarks", "vesting/runtime-benchmarks", "bonded-finance/runtime-benchmarks", "common/runtime-benchmarks", "asset-tx-payment/runtime-benchmarks", "proxy/runtime-benchmarks", "pablo/runtime-benchmarks", "oracle/runtime-benchmarks", "pallet-ibc/runtime-benchmarks"] -std = ["codec/std", "sp-api/std", "sp-std/std", "sp-core/std", "sp-runtime/std", "sp-version/std", "sp-offchain/std", "sp-session/std", "sp-block-builder/std", "sp-transaction-pool/std", "sp-inherents/std", "frame-support/std", "executive/std", "frame-system/std", "utility/std", "authorship/std", "balances/std", "randomness-collective-flip/std", "timestamp/std", "session/std", "sudo/std", "indices/std", "identity/std", "multisig/std", "call-filter/std", "orml-tokens/std", "orml-traits/std", "treasury/std", "democracy/std", "scheduler/std", "common/std", "primitives/std", "collective/std", "transaction-payment/std", "parachain-info/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", "cumulus-primitives-core/std", "cumulus-primitives-timestamp/std", "cumulus-primitives-utility/std", "collator-selection/std", "xcm/std", "xcm-builder/std", "xcm-executor/std", "aura/std", "sp-consensus-aura/std", "scale-info/std", "orml-xtokens/std", "orml-xcm-support/std", "orml-unknown-tokens/std", "composable-traits/std", "composable-support/std", "governance-registry/std", "currency-factory/std", "assets/std", "assets-transactor-router/std", "assets-registry/std", "vesting/std", "bonded-finance/std", "crowdloan-rewards/std", "preimage/std", "membership/std", "system-rpc-runtime-api/std", "transaction-payment-rpc-runtime-api/std", "assets-runtime-api/std", "crowdloan-rewards-runtime-api/std", "asset-tx-payment/std", "proxy/std", "pablo/std", "oracle/std", "pablo-runtime-api/std", "ibc/std", "pallet-ibc/std", "ibc-primitives/std", "ibc-runtime-api/std", "reward/std", "farming/std"] +std = ["codec/std", "sp-api/std", "sp-std/std", "sp-core/std", "sp-runtime/std", "sp-version/std", "sp-offchain/std", "sp-session/std", "sp-block-builder/std", "sp-transaction-pool/std", "sp-inherents/std", "frame-support/std", "executive/std", "frame-system/std", "utility/std", "authorship/std", "balances/std", "randomness-collective-flip/std", "timestamp/std", "session/std", "sudo/std", "indices/std", "identity/std", "multisig/std", "call-filter/std", "orml-tokens/std", "orml-traits/std", "treasury/std", "democracy/std", "scheduler/std", "common/std", "primitives/std", "collective/std", "transaction-payment/std", "parachain-info/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-xcm/std", "cumulus-primitives-core/std", "cumulus-primitives-timestamp/std", "cumulus-primitives-utility/std", "collator-selection/std", "xcm/std", "xcm-builder/std", "xcm-executor/std", "aura/std", "sp-consensus-aura/std", "scale-info/std", "orml-xtokens/std", "orml-xcm-support/std", "orml-unknown-tokens/std", "composable-traits/std", "composable-support/std", "governance-registry/std", "currency-factory/std", "assets/std", "assets-transactor-router/std", "assets-registry/std", "vesting/std", "bonded-finance/std", "crowdloan-rewards/std", "preimage/std", "membership/std", "system-rpc-runtime-api/std", "transaction-payment-rpc-runtime-api/std", "assets-runtime-api/std", "crowdloan-rewards-runtime-api/std", "asset-tx-payment/std", "proxy/std", "pablo/std", "oracle/std", "pablo-runtime-api/std", "ibc/std", "pallet-ibc/std", "ibc-primitives/std", "ibc-runtime-api/std", "reward/std", "farming/std", "reward-rpc-runtime-api/std"] From 2e3d9ebc9b0c60324e94935f663f4012ad297aee Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 18:52:37 +0100 Subject: [PATCH 11/25] add reward-rpc implementation and add dependency to parachain node --- code/Cargo.lock | 13 ++ code/parachain/frame/reward/rpc/Cargo.toml | 13 ++ code/parachain/frame/reward/rpc/src/lib.rs | 215 +++++++++++++++++++++ code/parachain/node/Cargo.toml | 2 + 4 files changed, 243 insertions(+) create mode 100644 code/parachain/frame/reward/rpc/Cargo.toml create mode 100644 code/parachain/frame/reward/rpc/src/lib.rs diff --git a/code/Cargo.lock b/code/Cargo.lock index 0fa046e48bd..43409cf6c8a 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -1799,6 +1799,7 @@ dependencies = [ "polkadot-primitives", "polkadot-service", "primitives", + "reward-rpc", "sc-basic-authorship", "sc-chain-spec", "sc-cli", @@ -12281,6 +12282,18 @@ dependencies = [ "sp-std 5.0.0", ] +[[package]] +name = "reward-rpc" +version = "0.3.0" +dependencies = [ + "jsonrpsee 0.16.2", + "parity-scale-codec", + "reward-rpc-runtime-api", + "sp-api", + "sp-blockchain", + "sp-runtime 7.0.0", +] + [[package]] name = "reward-rpc-runtime-api" version = "0.3.0" diff --git a/code/parachain/frame/reward/rpc/Cargo.toml b/code/parachain/frame/reward/rpc/Cargo.toml new file mode 100644 index 00000000000..f4dba87699e --- /dev/null +++ b/code/parachain/frame/reward/rpc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["Composable Developers"] +edition = "2021" +name = "reward-rpc" +version = '0.3.0' + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["server", "macros"] } +sp-runtime = { workspace = true } +sp-api = { workspace = true } +sp-blockchain = { workspace = true } +reward-rpc-runtime-api = { path = "runtime-api" } diff --git a/code/parachain/frame/reward/rpc/src/lib.rs b/code/parachain/frame/reward/rpc/src/lib.rs new file mode 100644 index 00000000000..15442f08ea2 --- /dev/null +++ b/code/parachain/frame/reward/rpc/src/lib.rs @@ -0,0 +1,215 @@ +//! RPC interface for the Reward Module. + +use codec::Codec; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; +use reward_rpc_runtime_api::BalanceWrapper; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, + DispatchError, +}; +use std::sync::Arc; + +pub use reward_rpc_runtime_api::RewardApi as RewardRuntimeApi; + +#[rpc(client, server)] +pub trait RewardApi +where + Balance: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec, + VaultId: Codec, + CurrencyId: Codec, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, +{ + #[method(name = "reward_computeEscrowReward")] + fn compute_escrow_reward( + &self, + account_id: AccountId, + currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; + + #[method(name = "reward_computeFarmingReward")] + fn compute_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; + + // TODO: change this to support querying by nominator_id + #[method(name = "reward_computeVaultReward")] + fn compute_vault_reward( + &self, + vault_id: VaultId, + currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; + + #[method(name = "reward_estimateEscrowRewardRate")] + fn estimate_escrow_reward_rate( + &self, + account_id: AccountId, + amount: Option, + lock_time: Option, + at: Option, + ) -> RpcResult; + + #[method(name = "reward_estimateFarmingReward")] + fn estimate_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; + + #[method(name = "reward_estimateVaultRewardRate")] + fn estimate_vault_reward_rate(&self, vault_id: VaultId, at: Option) -> RpcResult; +} + +fn internal_err(message: T) -> JsonRpseeError { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + message.to_string(), + None::<()>, + ))) +} + +/// A struct that implements the [`RewardApi`]. +pub struct Reward { + client: Arc, + _marker: std::marker::PhantomData, +} + +impl Reward { + /// Create new `Reward` with the given reference to the client. + pub fn new(client: Arc) -> Self { + Reward { + client, + _marker: Default::default(), + } + } +} + +fn handle_response(result: Result, E>, msg: String) -> RpcResult { + result + .map_err(|err| internal_err(format!("Runtime error: {:?}: {:?}", msg, err)))? + .map_err(|err| internal_err(format!("Execution error: {:?}: {:?}", msg, err))) +} + +#[async_trait] +impl + RewardApiServer<::Hash, AccountId, VaultId, CurrencyId, Balance, BlockNumber, UnsignedFixedPoint> + for Reward +where + Block: BlockT, + C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + C::Api: RewardRuntimeApi, + AccountId: Codec, + VaultId: Codec, + CurrencyId: Codec, + Balance: Codec + MaybeDisplay + MaybeFromStr, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, +{ + fn compute_escrow_reward( + &self, + account_id: AccountId, + currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.compute_escrow_reward(&at, account_id, currency_id), + "Unable to compute the current reward".into(), + ) + } + + fn compute_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.compute_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), + "Unable to compute the current reward".into(), + ) + } + + fn compute_vault_reward( + &self, + vault_id: VaultId, + currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.compute_vault_reward(&at, vault_id, currency_id), + "Unable to compute the current reward".into(), + ) + } + + fn estimate_escrow_reward_rate( + &self, + account_id: AccountId, + amount: Option, + lock_time: Option, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.estimate_escrow_reward_rate(&at, account_id, amount, lock_time), + "Unable to estimate the current reward".into(), + ) + } + + fn estimate_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.estimate_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), + "Unable to estimate the current reward".into(), + ) + } + + fn estimate_vault_reward_rate( + &self, + vault_id: VaultId, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + handle_response( + api.estimate_vault_reward_rate(&at, vault_id), + "Unable to estimate the current reward".into(), + ) + } +} diff --git a/code/parachain/node/Cargo.toml b/code/parachain/node/Cargo.toml index 530f3a73625..c5d8ce137e2 100644 --- a/code/parachain/node/Cargo.toml +++ b/code/parachain/node/Cargo.toml @@ -39,6 +39,8 @@ staking-rewards-runtime-api = { path = "../frame/staking-rewards/runtime-api" } pallet-transaction-payment-rpc = { path = "../frame/transaction-payment/rpc" } pallet-transaction-payment-rpc-runtime-api = { path = "../frame/transaction-payment/rpc/runtime-api" } +reward-rpc = { path = "../frame/reward/rpc" } + ibc-rpc = { workspace = true } pallet-ibc = { workspace = true } From feeea35b00795e6800013c9219409088b10e227f Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 21:22:28 +0100 Subject: [PATCH 12/25] update RewardApi for reward pallet --- .../frame/reward/rpc/runtime-api/src/lib.rs | 15 +-- code/parachain/frame/reward/rpc/src/lib.rs | 99 +------------------ 2 files changed, 5 insertions(+), 109 deletions(-) diff --git a/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs index 0d55c343c8d..2ac3b08ed85 100644 --- a/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs +++ b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs @@ -35,30 +35,17 @@ fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>(dese } sp_api::decl_runtime_apis! { - pub trait RewardApi where + pub trait RewardApi where AccountId: Codec, - VaultId: Codec, CurrencyId: Codec, Balance: Codec, BlockNumber: Codec, UnsignedFixedPoint: Codec, { - /// Calculate the number of escrow rewards accrued - fn compute_escrow_reward(account_id: AccountId, currency_id: CurrencyId) -> Result, DispatchError>; - /// Calculate the number of farming rewards accrued fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; - /// Calculate the number of vault rewards accrued - fn compute_vault_reward(vault_id: VaultId, currency_id: CurrencyId) -> Result, DispatchError>; - - /// Estimate staking reward rate for a one year period - fn estimate_escrow_reward_rate(account_id: AccountId, amount: Option, lock_time: Option) -> Result; - /// Estimate farming rewards for remaining incentives fn estimate_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; - - /// Estimate vault reward rate for a one year period - fn estimate_vault_reward_rate(vault_id: VaultId) -> Result; } } diff --git a/code/parachain/frame/reward/rpc/src/lib.rs b/code/parachain/frame/reward/rpc/src/lib.rs index 15442f08ea2..d7fb22b3e35 100644 --- a/code/parachain/frame/reward/rpc/src/lib.rs +++ b/code/parachain/frame/reward/rpc/src/lib.rs @@ -19,23 +19,14 @@ use std::sync::Arc; pub use reward_rpc_runtime_api::RewardApi as RewardRuntimeApi; #[rpc(client, server)] -pub trait RewardApi +pub trait RewardApi where Balance: Codec + MaybeDisplay + MaybeFromStr, AccountId: Codec, - VaultId: Codec, CurrencyId: Codec, BlockNumber: Codec, UnsignedFixedPoint: Codec, { - #[method(name = "reward_computeEscrowReward")] - fn compute_escrow_reward( - &self, - account_id: AccountId, - currency_id: CurrencyId, - at: Option, - ) -> RpcResult>; - #[method(name = "reward_computeFarmingReward")] fn compute_farming_reward( &self, @@ -45,24 +36,6 @@ where at: Option, ) -> RpcResult>; - // TODO: change this to support querying by nominator_id - #[method(name = "reward_computeVaultReward")] - fn compute_vault_reward( - &self, - vault_id: VaultId, - currency_id: CurrencyId, - at: Option, - ) -> RpcResult>; - - #[method(name = "reward_estimateEscrowRewardRate")] - fn estimate_escrow_reward_rate( - &self, - account_id: AccountId, - amount: Option, - lock_time: Option, - at: Option, - ) -> RpcResult; - #[method(name = "reward_estimateFarmingReward")] fn estimate_farming_reward( &self, @@ -71,9 +44,6 @@ where reward_currency_id: CurrencyId, at: Option, ) -> RpcResult>; - - #[method(name = "reward_estimateVaultRewardRate")] - fn estimate_vault_reward_rate(&self, vault_id: VaultId, at: Option) -> RpcResult; } fn internal_err(message: T) -> JsonRpseeError { @@ -107,35 +77,19 @@ fn handle_response(result: Result - RewardApiServer<::Hash, AccountId, VaultId, CurrencyId, Balance, BlockNumber, UnsignedFixedPoint> +impl + RewardApiServer<::Hash, AccountId, CurrencyId, Balance, BlockNumber, UnsignedFixedPoint> for Reward where Block: BlockT, C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: RewardRuntimeApi, + C::Api: RewardRuntimeApi, AccountId: Codec, - VaultId: Codec, CurrencyId: Codec, Balance: Codec + MaybeDisplay + MaybeFromStr, BlockNumber: Codec, UnsignedFixedPoint: Codec, { - fn compute_escrow_reward( - &self, - account_id: AccountId, - currency_id: CurrencyId, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - - handle_response( - api.compute_escrow_reward(&at, account_id, currency_id), - "Unable to compute the current reward".into(), - ) - } - fn compute_farming_reward( &self, account_id: AccountId, @@ -152,37 +106,6 @@ where ) } - fn compute_vault_reward( - &self, - vault_id: VaultId, - currency_id: CurrencyId, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - - handle_response( - api.compute_vault_reward(&at, vault_id, currency_id), - "Unable to compute the current reward".into(), - ) - } - - fn estimate_escrow_reward_rate( - &self, - account_id: AccountId, - amount: Option, - lock_time: Option, - at: Option<::Hash>, - ) -> RpcResult { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - - handle_response( - api.estimate_escrow_reward_rate(&at, account_id, amount, lock_time), - "Unable to estimate the current reward".into(), - ) - } - fn estimate_farming_reward( &self, account_id: AccountId, @@ -198,18 +121,4 @@ where "Unable to estimate the current reward".into(), ) } - - fn estimate_vault_reward_rate( - &self, - vault_id: VaultId, - at: Option<::Hash>, - ) -> RpcResult { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - - handle_response( - api.estimate_vault_reward_rate(&at, vault_id), - "Unable to estimate the current reward".into(), - ) - } } From b410d6d1f44ce3fcbbedaf663389d92e42ff9a6a Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 21:38:37 +0100 Subject: [PATCH 13/25] add reward_rpc_runtime_api::RewardApi to picasso-runtime --- code/parachain/runtime/picasso/src/lib.rs | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 9a2b1b53e1e..1a815ec16c8 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -37,6 +37,7 @@ pub use common::xcmp::{MaxInstructions, UnitWeightCost}; pub use fees::{AssetsPaymentHeader, FinalPriceConverter}; use version::{Version, VERSION}; pub use xcmp::XcmConfig; +use frame_support::dispatch::DispatchError; pub use crate::fees::WellKnownForeignToNativePriceConverter; @@ -1143,6 +1144,32 @@ impl_runtime_apis! { } } + impl reward_rpc_runtime_api::RewardApi< + Block, + AccountId, + CurrencyId, + Balance, + BlockNumber, + sp_runtime::FixedU128 + > for Runtime { + fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError> { + let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; + let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; + Ok(balance) + } + fn estimate_farming_reward( + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + ) -> Result, DispatchError> { + >::withdraw_reward(&pool_currency_id, &account_id, reward_currency_id)?; + >::distribute_reward(&pool_currency_id, reward_currency_id, Farming::total_rewards(&pool_currency_id, &reward_currency_id))?; + let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; + let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; + Ok(balance) + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { From 4459a17fcaa03a319b6bb8e540d453baf6b951e3 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 22:04:21 +0100 Subject: [PATCH 14/25] Complete Farming Api to picasso node --- code/parachain/node/src/rpc.rs | 8 +++++++- code/parachain/node/src/runtime.rs | 15 +++++++++++++++ code/parachain/node/src/service.rs | 3 ++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/code/parachain/node/src/rpc.rs b/code/parachain/node/src/rpc.rs index b8335046909..7f2eb145828 100644 --- a/code/parachain/node/src/rpc.rs +++ b/code/parachain/node/src/rpc.rs @@ -22,7 +22,7 @@ use crate::{ runtime::{ assets::ExtendWithAssetsApi, cosmwasm::ExtendWithCosmwasmApi, crowdloan_rewards::ExtendWithCrowdloanRewardsApi, ibc::ExtendWithIbcApi, - lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, + lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, farming::ExtendWithFarmingApi, staking_rewards::ExtendWithStakingRewardsApi, BaseHostRuntimeApis, }, }; @@ -67,6 +67,7 @@ where + ExtendWithAssetsApi + ExtendWithCrowdloanRewardsApi + ExtendWithPabloApi + + ExtendWithFarmingApi + ExtendWithLendingApi + ExtendWithCosmwasmApi + ExtendWithIbcApi, @@ -95,6 +96,11 @@ where deps.clone(), )?; + as ProvideRuntimeApi>::Api::extend_with_farming_api( + &mut io, + deps.clone(), + )?; + as ProvideRuntimeApi>::Api::extend_with_lending_api( &mut io, deps.clone(), diff --git a/code/parachain/node/src/runtime.rs b/code/parachain/node/src/runtime.rs index d25739a346e..02d7491a043 100644 --- a/code/parachain/node/src/runtime.rs +++ b/code/parachain/node/src/runtime.rs @@ -4,6 +4,7 @@ use crowdloan_rewards_rpc::{CrowdloanRewards, CrowdloanRewardsApiServer}; use cumulus_primitives_core::CollectCollationInfo; use ibc_rpc::{IbcApiServer, IbcRpcHandler}; use pablo_rpc::{Pablo, PabloApiServer}; +use reward_rpc::{Reward, RewardApiServer}; use pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi; use sp_api::{ApiExt, Metadata, StateBackend}; use sp_block_builder::BlockBuilder; @@ -195,6 +196,20 @@ define_trait! { } } + mod farming { + pub trait ExtendWithFarmingApi { + fn extend_with_farming_api(io, deps); + } + + impl for composable_runtime {} + + impl for picasso_runtime { + fn (io, deps) { + io.merge(Reward::new(deps.client).into_rpc()) + } + } + } + mod lending { pub trait ExtendWithLendingApi { fn extend_with_lending_api(io, deps); diff --git a/code/parachain/node/src/service.rs b/code/parachain/node/src/service.rs index 9e537312303..65b404015ac 100644 --- a/code/parachain/node/src/service.rs +++ b/code/parachain/node/src/service.rs @@ -6,7 +6,7 @@ use crate::{ runtime::{ assets::ExtendWithAssetsApi, cosmwasm::ExtendWithCosmwasmApi, crowdloan_rewards::ExtendWithCrowdloanRewardsApi, ibc::ExtendWithIbcApi, - lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, + lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, farming::ExtendWithFarmingApi, staking_rewards::ExtendWithStakingRewardsApi, BaseHostRuntimeApis, }, }; @@ -268,6 +268,7 @@ where + ExtendWithAssetsApi + ExtendWithCrowdloanRewardsApi + ExtendWithPabloApi + + ExtendWithFarmingApi + ExtendWithLendingApi + ExtendWithCosmwasmApi + ExtendWithIbcApi, From f9a6fe648b5b18c8cdc2e8c23194d65fe534f635 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 27 Apr 2023 22:14:34 +0100 Subject: [PATCH 15/25] add runtime-benchmarks and try-runtime features to reward and farming --- code/Cargo.lock | 2 ++ code/parachain/frame/farming/Cargo.toml | 17 ++++++++--------- code/parachain/frame/reward/Cargo.toml | 16 ++++++++-------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/code/Cargo.lock b/code/Cargo.lock index 43409cf6c8a..f5ef23811e6 100644 --- a/code/Cargo.lock +++ b/code/Cargo.lock @@ -3774,6 +3774,7 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" name = "farming" version = "1.0.0" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "log 0.4.17", @@ -12267,6 +12268,7 @@ dependencies = [ name = "reward" version = "1.0.0" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "log 0.4.17", diff --git a/code/parachain/frame/farming/Cargo.toml b/code/parachain/frame/farming/Cargo.toml index fcf543e7fab..48d2d2422c4 100644 --- a/code/parachain/frame/farming/Cargo.toml +++ b/code/parachain/frame/farming/Cargo.toml @@ -27,14 +27,13 @@ sp-std = { default-features = false, workspace = true } frame-support = { default-features = false, workspace = true } frame-system = { default-features = false, workspace = true } -# frame-benchmarking = { default-features = false, workspace = true, optional = true } +frame-benchmarking = { default-features = false, workspace = true, optional = true } [dev-dependencies] pallet-timestamp = { workspace = true } -# primitives = { path = "../../runtime/primitives", default-features = false } pallet-balances = { workspace = true, default-features = false } -# frame-benchmarking = { default-features = false, workspace = true } +frame-benchmarking = { default-features = false, workspace = true } [features] default = ["std"] @@ -53,9 +52,9 @@ std = [ "frame-system/std", ] -# runtime-benchmarks = [ -# "frame-benchmarking", -# "frame-support/runtime-benchmarks", -# "frame-system/runtime-benchmarks", -# ] -# try-runtime = [ "frame-support/try-runtime" ] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ "frame-support/try-runtime" ] diff --git a/code/parachain/frame/reward/Cargo.toml b/code/parachain/frame/reward/Cargo.toml index a604376838f..8ef302b8aba 100644 --- a/code/parachain/frame/reward/Cargo.toml +++ b/code/parachain/frame/reward/Cargo.toml @@ -22,13 +22,13 @@ sp-std = { default-features = false, workspace = true } frame-support = { default-features = false, workspace = true } frame-system = { default-features = false, workspace = true } -# frame-benchmarking = { default-features = false, workspace = true, optional = true } +frame-benchmarking = { default-features = false, workspace = true, optional = true } [dev-dependencies] pallet-timestamp = { workspace = true } rand = "0.8.3" -# frame-benchmarking = { default-features = false, workspace = true } +frame-benchmarking = { default-features = false, workspace = true } [features] default = ["std"] @@ -47,9 +47,9 @@ std = [ "frame-system/std", ] -# runtime-benchmarks = [ -# "frame-benchmarking", -# "frame-support/runtime-benchmarks", -# "frame-system/runtime-benchmarks", -# ] -# try-runtime = [ "frame-support/try-runtime" ] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ "frame-support/try-runtime" ] From cc457bb6a1665404fb23b1ebe43bf3da551c0f71 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Fri, 28 Apr 2023 14:07:59 +0100 Subject: [PATCH 16/25] update farming and reuse T::AssetId --- code/parachain/frame/farming/src/lib.rs | 73 ++++++++++++++--------- code/parachain/frame/farming/src/mock.rs | 1 + code/parachain/frame/reward/src/lib.rs | 3 +- code/parachain/runtime/picasso/src/lib.rs | 1 + 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 79d57153ac8..f9cb44244ef 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -38,16 +38,16 @@ mod mock; mod tests; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode, MaxEncodedLen, FullCodec}; use frame_support::{dispatch::DispatchResult, traits::Get, transactional, weights::Weight, PalletId, RuntimeDebug}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; -// use primitives::CurrencyId; use reward::RewardsApi; use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, AtLeast32Bit, CheckedDiv, Saturating, Zero}, ArithmeticError, DispatchError, }; +use core::fmt::Debug; use sp_std::vec::Vec; pub use pallet::*; @@ -86,8 +86,7 @@ pub mod pallet { pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type CurrencyIdOf = - <::MultiCurrency as MultiCurrency<::AccountId>>::CurrencyId; + pub(crate) type AssetIdOf = ::AssetId; pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; @@ -114,17 +113,33 @@ pub mod pallet { /// Reward pools to track stake. type RewardPools: RewardsApi< - CurrencyIdOf, // pool id is the lp token + AssetIdOf, // pool id is the lp token AccountIdOf, BalanceOf, - CurrencyId = CurrencyIdOf, + CurrencyId = AssetIdOf, >; + type AssetId: FullCodec + + MaxEncodedLen + + Eq + + PartialEq + + Copy + + Clone + + MaybeSerializeDeserialize + + Debug + + Default + + TypeInfo + + From + + Into + + Ord; + /// Currency handler to transfer tokens. - type MultiCurrency: MultiReservableCurrency>; - // type MultiCurrency: MultiReservableCurrency, CurrencyId = CurrencyId>; + type MultiCurrency: MultiReservableCurrency< + AccountIdOf, + CurrencyId = Self::AssetId, + >; - /// Weight information for the extrinsics. //TODO + /// Weight information for the extrinsics. type WeightInfo: WeightInfo; } @@ -133,20 +148,20 @@ pub mod pallet { #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { RewardScheduleUpdated { - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, period_count: u32, per_period: BalanceOf, }, RewardDistributed { - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, amount: BalanceOf, }, RewardClaimed { account_id: AccountIdOf, - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, amount: BalanceOf, }, } @@ -193,9 +208,9 @@ pub mod pallet { pub type RewardSchedules = StorageDoubleMap< _, Blake2_128Concat, - CurrencyIdOf, // lp token + AssetIdOf, // lp token Blake2_128Concat, - CurrencyIdOf, // reward currency + AssetIdOf, // reward currency RewardScheduleOf, ValueQuery, >; @@ -216,8 +231,8 @@ pub mod pallet { #[transactional] pub fn update_reward_schedule( origin: OriginFor, - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, period_count: u32, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { @@ -257,8 +272,8 @@ pub mod pallet { #[transactional] pub fn remove_reward_schedule( origin: OriginFor, - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; // pool_currency_id.sort(); @@ -288,7 +303,7 @@ pub mod pallet { #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::deposit())] #[transactional] - pub fn deposit(origin: OriginFor, pool_currency_id: CurrencyIdOf) -> DispatchResult { + pub fn deposit(origin: OriginFor, pool_currency_id: AssetIdOf) -> DispatchResult { let who = ensure_signed(origin)?; // pool_currency_id.sort(); @@ -306,7 +321,7 @@ pub mod pallet { #[transactional] pub fn withdraw( origin: OriginFor, - pool_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -326,8 +341,8 @@ pub mod pallet { #[transactional] pub fn claim( origin: OriginFor, - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, ) -> DispatchResult { let who = ensure_signed(origin)?; // pool_currency_id.sort(); @@ -352,11 +367,11 @@ pub mod pallet { // "Internal" functions, callable by code. impl Pallet { - pub fn pool_account_id(pool_currency_id: &CurrencyIdOf) -> T::AccountId { + pub fn pool_account_id(pool_currency_id: &AssetIdOf) -> T::AccountId { T::FarmingPalletId::get().into_sub_account_truncating(pool_currency_id) } - pub fn total_rewards(pool_currency_id: &CurrencyIdOf, reward_currency_id: &CurrencyIdOf) -> BalanceOf { + pub fn total_rewards(pool_currency_id: &AssetIdOf, reward_currency_id: &AssetIdOf) -> BalanceOf { let pool_currency_id = pool_currency_id.clone(); // pool_currency_id.sort(); RewardSchedules::::get(pool_currency_id, reward_currency_id) @@ -366,8 +381,8 @@ impl Pallet { #[transactional] fn try_distribute_reward( - pool_currency_id: CurrencyIdOf, - reward_currency_id: CurrencyIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, amount: BalanceOf, ) -> Result<(), DispatchError> { T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount) diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs index 79e4fc10847..5416d55c5e0 100644 --- a/code/parachain/frame/farming/src/mock.rs +++ b/code/parachain/frame/farming/src/mock.rs @@ -122,6 +122,7 @@ impl Config for Test { type TreasuryAccountId = TreasuryAccountId; type RewardPeriod = RewardPeriod; type RewardPools = Rewards; + type AssetId = CurrencyId; type MultiCurrency = Tokens; type WeightInfo = (); } diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index 35bc895b640..5f5295329ae 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -14,8 +14,7 @@ mod tests; use codec::{Decode, Encode, EncodeLike}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, - ensure, - traits::Get, + ensure }; use scale_info::TypeInfo; diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 1a815ec16c8..bdc8aef067e 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -301,6 +301,7 @@ parameter_types! { impl farming::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type AssetId = CurrencyId; type FarmingPalletId = FarmingPalletId; type TreasuryAccountId = FarmingAccount; type RewardPeriod = RewardPeriod; From 01313417f6d3adafa96a99794f0ba9cb3d0d1e83 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Fri, 28 Apr 2023 14:17:30 +0100 Subject: [PATCH 17/25] update std feature in reward and farming pallet --- code/parachain/frame/farming/Cargo.toml | 10 +++++----- code/parachain/frame/reward/Cargo.toml | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/code/parachain/frame/farming/Cargo.toml b/code/parachain/frame/farming/Cargo.toml index 48d2d2422c4..4ed127ae57e 100644 --- a/code/parachain/frame/farming/Cargo.toml +++ b/code/parachain/frame/farming/Cargo.toml @@ -33,7 +33,7 @@ frame-benchmarking = { default-features = false, workspace = true, optional = tr [dev-dependencies] pallet-timestamp = { workspace = true } pallet-balances = { workspace = true, default-features = false } -frame-benchmarking = { default-features = false, workspace = true } +# frame-benchmarking = { default-features = false, workspace = true } [features] default = ["std"] @@ -50,11 +50,11 @@ std = [ "frame-support/std", "frame-system/std", + "frame-benchmarking/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] -try-runtime = [ "frame-support/try-runtime" ] diff --git a/code/parachain/frame/reward/Cargo.toml b/code/parachain/frame/reward/Cargo.toml index 8ef302b8aba..fc554536239 100644 --- a/code/parachain/frame/reward/Cargo.toml +++ b/code/parachain/frame/reward/Cargo.toml @@ -33,7 +33,7 @@ frame-benchmarking = { default-features = false, workspace = true } [features] default = ["std"] std = [ - "log/std", + "log/std", "serde", "codec/std", @@ -45,11 +45,11 @@ std = [ "frame-support/std", "frame-system/std", + "frame-benchmarking/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", -] -try-runtime = [ "frame-support/try-runtime" ] + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] \ No newline at end of file From 6431cdd34431a0f92a1ab2935d3db4e44bec16a7 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Mon, 1 May 2023 15:18:43 +0100 Subject: [PATCH 18/25] add logs when update reward scheduled failed by overflow --- code/parachain/frame/farming/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index f9cb44244ef..571e7256509 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -248,7 +248,13 @@ pub mod pallet { let total_period_count = reward_schedule .period_count .checked_add(period_count) - .ok_or(ArithmeticError::Overflow)?; + .ok_or_else(|| { + log::error!("Overflow error: Failed to calculate total_period_count for pool_currency_id : {:?}, reward_currency_id : {:?}, old period_count : {}, extend period_count : {}", + pool_currency_id, reward_currency_id, reward_schedule.period_count, period_count + ); + ArithmeticError::Overflow + })?; + let total_free = T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id); let total_per_period = total_free.checked_div(&total_period_count.into()).unwrap_or_default(); From b97e7e160d7559a59d714b731cc412c0316519ec Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Mon, 1 May 2023 15:19:54 +0100 Subject: [PATCH 19/25] remove pool_currency_id.sort().it is not necessary according to AssetId --- code/parachain/frame/farming/src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 571e7256509..8fd505455cc 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -237,8 +237,6 @@ pub mod pallet { #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { ensure_root(origin)?; - // pool_currency_id.sort(); - // fund the pool account from treasury let treasury_account_id = T::TreasuryAccountId::get(); let pool_account_id = Self::pool_account_id(&pool_currency_id); @@ -282,8 +280,6 @@ pub mod pallet { reward_currency_id: AssetIdOf, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - // pool_currency_id.sort(); - // transfer unspent rewards to treasury let treasury_account_id = T::TreasuryAccountId::get(); let pool_account_id = Self::pool_account_id(&pool_currency_id); @@ -311,8 +307,6 @@ pub mod pallet { #[transactional] pub fn deposit(origin: OriginFor, pool_currency_id: AssetIdOf) -> DispatchResult { let who = ensure_signed(origin)?; - // pool_currency_id.sort(); - // reserve lp tokens to prevent spending let amount = T::MultiCurrency::free_balance(pool_currency_id.clone(), &who); T::MultiCurrency::reserve(pool_currency_id.clone(), &who, amount)?; @@ -331,8 +325,6 @@ pub mod pallet { amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - // pool_currency_id.sort(); - // unreserve lp tokens to allow spending let remaining = T::MultiCurrency::unreserve(pool_currency_id.clone(), &who, amount); ensure!(remaining.is_zero(), Error::::InsufficientStake); @@ -351,7 +343,6 @@ pub mod pallet { reward_currency_id: AssetIdOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - // pool_currency_id.sort(); let pool_account_id = Self::pool_account_id(&pool_currency_id); // get reward from staking pool @@ -379,7 +370,6 @@ impl Pallet { pub fn total_rewards(pool_currency_id: &AssetIdOf, reward_currency_id: &AssetIdOf) -> BalanceOf { let pool_currency_id = pool_currency_id.clone(); - // pool_currency_id.sort(); RewardSchedules::::get(pool_currency_id, reward_currency_id) .total() .unwrap_or_default() From bb1d22520e8c7f0c94dc2d942cc5645f2660458b Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 3 May 2023 19:18:09 +0100 Subject: [PATCH 20/25] add #[allow(clippy::disallowed_types)] because of ValueQuery --- code/parachain/frame/farming/src/lib.rs | 9 +++++---- code/parachain/frame/reward/src/lib.rs | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 8fd505455cc..90e56b9e407 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -205,6 +205,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn reward_schedules)] + #[allow(clippy::disallowed_types)] pub type RewardSchedules = StorageDoubleMap< _, Blake2_128Concat, @@ -308,8 +309,8 @@ pub mod pallet { pub fn deposit(origin: OriginFor, pool_currency_id: AssetIdOf) -> DispatchResult { let who = ensure_signed(origin)?; // reserve lp tokens to prevent spending - let amount = T::MultiCurrency::free_balance(pool_currency_id.clone(), &who); - T::MultiCurrency::reserve(pool_currency_id.clone(), &who, amount)?; + let amount = T::MultiCurrency::free_balance(pool_currency_id, &who); + T::MultiCurrency::reserve(pool_currency_id, &who, amount)?; // deposit lp tokens as stake T::RewardPools::deposit_stake(&pool_currency_id, &who, amount) @@ -326,7 +327,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; // unreserve lp tokens to allow spending - let remaining = T::MultiCurrency::unreserve(pool_currency_id.clone(), &who, amount); + let remaining = T::MultiCurrency::unreserve(pool_currency_id, &who, amount); ensure!(remaining.is_zero(), Error::::InsufficientStake); // withdraw lp tokens from stake @@ -369,7 +370,7 @@ impl Pallet { } pub fn total_rewards(pool_currency_id: &AssetIdOf, reward_currency_id: &AssetIdOf) -> BalanceOf { - let pool_currency_id = pool_currency_id.clone(); + let pool_currency_id = pool_currency_id; RewardSchedules::::get(pool_currency_id, reward_currency_id) .total() .unwrap_or_default() diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index 5f5295329ae..03348ef6128 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -131,6 +131,7 @@ pub mod pallet { /// The total stake deposited to this reward pool. #[pallet::storage] #[pallet::getter(fn total_stake)] + #[allow(clippy::disallowed_types)] pub type TotalStake, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::PoolId, SignedFixedPoint, ValueQuery>; @@ -138,12 +139,14 @@ pub mod pallet { /// NOTE: this is currently only used for integration tests. #[pallet::storage] #[pallet::getter(fn total_rewards)] + #[allow(clippy::disallowed_types)] pub type TotalRewards, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CurrencyId, SignedFixedPoint, ValueQuery>; /// Used to compute the rewards for a participant's stake. #[pallet::storage] #[pallet::getter(fn reward_per_token)] + #[allow(clippy::disallowed_types)] pub type RewardPerToken, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -156,11 +159,13 @@ pub mod pallet { /// The stake of a participant in this reward pool. #[pallet::storage] + #[allow(clippy::disallowed_types)] pub type Stake, I: 'static = ()> = StorageMap<_, Blake2_128Concat, (T::PoolId, T::StakeId), SignedFixedPoint, ValueQuery>; /// Accounts for previous changes in stake size. #[pallet::storage] + #[allow(clippy::disallowed_types)] pub type RewardTally, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, @@ -173,6 +178,7 @@ pub mod pallet { /// Track the currencies used for rewards. #[pallet::storage] + #[allow(clippy::disallowed_types)] pub type RewardCurrencies, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::PoolId, BTreeSet, ValueQuery>; From cac523de79d7dffb205afcd337f5aba85cf9d744 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 3 May 2023 19:33:58 +0100 Subject: [PATCH 21/25] cargo +nightly fmt --all -- --- .../frame/farming/src/benchmarking.rs | 187 ++-- code/parachain/frame/farming/src/lib.rs | 624 +++++------ code/parachain/frame/farming/src/mock.rs | 173 ++- code/parachain/frame/farming/src/tests.rs | 419 ++++---- .../frame/reward/rpc/runtime-api/src/lib.rs | 52 +- code/parachain/frame/reward/rpc/src/lib.rs | 173 +-- code/parachain/frame/reward/src/lib.rs | 988 +++++++++--------- code/parachain/frame/reward/src/mock.rs | 108 +- code/parachain/frame/reward/src/tests.rs | 206 ++-- code/parachain/node/src/rpc.rs | 4 +- code/parachain/node/src/runtime.rs | 2 +- code/parachain/node/src/service.rs | 4 +- code/parachain/runtime/picasso/src/lib.rs | 76 +- 13 files changed, 1528 insertions(+), 1488 deletions(-) diff --git a/code/parachain/frame/farming/src/benchmarking.rs b/code/parachain/frame/farming/src/benchmarking.rs index 4091ee61fea..2968992e1e3 100644 --- a/code/parachain/frame/farming/src/benchmarking.rs +++ b/code/parachain/frame/farming/src/benchmarking.rs @@ -11,113 +11,114 @@ use crate::Pallet as Farming; use frame_system::Pallet as System; fn default_reward_schedule(reward_currency_id: CurrencyId) -> RewardScheduleOf { - let reward_schedule = RewardSchedule { - period_count: 100u32, - per_period: 1000u32.into(), - }; - let total_amount = reward_schedule.total().unwrap(); - - assert_ok!(T::MultiCurrency::deposit( - reward_currency_id, - &T::TreasuryAccountId::get(), - total_amount, - )); - - reward_schedule + let reward_schedule = RewardSchedule { period_count: 100u32, per_period: 1000u32.into() }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(T::MultiCurrency::deposit( + reward_currency_id, + &T::TreasuryAccountId::get(), + total_amount, + )); + + reward_schedule } fn create_reward_schedule(pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) { - let reward_schedule = default_reward_schedule::(reward_currency_id); - - assert_ok!(Farming::::update_reward_schedule( - RawOrigin::Root.into(), - pool_currency_id, - reward_currency_id, - reward_schedule.period_count, - reward_schedule.total().unwrap(), - )); + let reward_schedule = default_reward_schedule::(reward_currency_id); + + assert_ok!(Farming::::update_reward_schedule( + RawOrigin::Root.into(), + pool_currency_id, + reward_currency_id, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); } fn create_default_reward_schedule() -> (CurrencyId, CurrencyId) { - let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); - let reward_currency_id = CurrencyId::Token(INTR); - create_reward_schedule::(pool_currency_id, reward_currency_id); - (pool_currency_id, reward_currency_id) + let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); + let reward_currency_id = CurrencyId::Token(INTR); + create_reward_schedule::(pool_currency_id, reward_currency_id); + (pool_currency_id, reward_currency_id) } -fn deposit_lp_tokens(pool_currency_id: CurrencyId, account_id: &T::AccountId, amount: BalanceOf) { - assert_ok!(T::MultiCurrency::deposit(pool_currency_id, account_id, amount)); - assert_ok!(Farming::::deposit( - RawOrigin::Signed(account_id.clone()).into(), - pool_currency_id, - )); +fn deposit_lp_tokens( + pool_currency_id: CurrencyId, + account_id: &T::AccountId, + amount: BalanceOf, +) { + assert_ok!(T::MultiCurrency::deposit(pool_currency_id, account_id, amount)); + assert_ok!(Farming::::deposit( + RawOrigin::Signed(account_id.clone()).into(), + pool_currency_id, + )); } pub fn get_benchmarking_currency_ids() -> Vec<(CurrencyId, CurrencyId)> { - vec![ - (Token(DOT), Token(INTR)), - (Token(KSM), Token(KINT)), - (Token(DOT), Token(IBTC)), - (Token(KSM), Token(KBTC)), - ] + vec![ + (Token(DOT), Token(INTR)), + (Token(KSM), Token(KINT)), + (Token(DOT), Token(IBTC)), + (Token(KSM), Token(KBTC)), + ] } benchmarks! { - on_initialize { - let c in 0 .. get_benchmarking_currency_ids().len() as u32; - let currency_ids = get_benchmarking_currency_ids(); - let block_number = T::RewardPeriod::get(); - - for i in 0 .. c { - let (pool_currency_id, reward_currency_id) = currency_ids[i as usize]; - create_reward_schedule::(pool_currency_id, reward_currency_id); - } - - Farming::::on_initialize(1u32.into()); - System::::set_block_number(block_number); - }: { - Farming::::on_initialize(System::::block_number()); - } - - update_reward_schedule { - let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); - let reward_currency_id = CurrencyId::Token(INTR); - let reward_schedule = default_reward_schedule::(reward_currency_id); - - }: _(RawOrigin::Root, pool_currency_id, reward_currency_id, reward_schedule.period_count, reward_schedule.total().unwrap()) - - remove_reward_schedule { - let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); - - }: _(RawOrigin::Root, pool_currency_id, reward_currency_id) - - deposit { - let origin: T::AccountId = account("Origin", 0, 0); - let (pool_currency_id, _) = create_default_reward_schedule::(); - assert_ok!(T::MultiCurrency::deposit( - pool_currency_id, - &origin, - 100u32.into(), - )); - - }: _(RawOrigin::Signed(origin), pool_currency_id) - - withdraw { - let origin: T::AccountId = account("Origin", 0, 0); - let (pool_currency_id, _) = create_default_reward_schedule::(); - let amount = 100u32.into(); - deposit_lp_tokens::(pool_currency_id, &origin, amount); - - }: _(RawOrigin::Signed(origin), pool_currency_id, amount) - - claim { - let origin: T::AccountId = account("Origin", 0, 0); - let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); - let amount = 100u32.into(); - deposit_lp_tokens::(pool_currency_id, &origin, amount); - assert_ok!(T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount)); - - }: _(RawOrigin::Signed(origin), pool_currency_id, reward_currency_id) + on_initialize { + let c in 0 .. get_benchmarking_currency_ids().len() as u32; + let currency_ids = get_benchmarking_currency_ids(); + let block_number = T::RewardPeriod::get(); + + for i in 0 .. c { + let (pool_currency_id, reward_currency_id) = currency_ids[i as usize]; + create_reward_schedule::(pool_currency_id, reward_currency_id); + } + + Farming::::on_initialize(1u32.into()); + System::::set_block_number(block_number); + }: { + Farming::::on_initialize(System::::block_number()); + } + + update_reward_schedule { + let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); + let reward_currency_id = CurrencyId::Token(INTR); + let reward_schedule = default_reward_schedule::(reward_currency_id); + + }: _(RawOrigin::Root, pool_currency_id, reward_currency_id, reward_schedule.period_count, reward_schedule.total().unwrap()) + + remove_reward_schedule { + let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); + + }: _(RawOrigin::Root, pool_currency_id, reward_currency_id) + + deposit { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, _) = create_default_reward_schedule::(); + assert_ok!(T::MultiCurrency::deposit( + pool_currency_id, + &origin, + 100u32.into(), + )); + + }: _(RawOrigin::Signed(origin), pool_currency_id) + + withdraw { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, _) = create_default_reward_schedule::(); + let amount = 100u32.into(); + deposit_lp_tokens::(pool_currency_id, &origin, amount); + + }: _(RawOrigin::Signed(origin), pool_currency_id, amount) + + claim { + let origin: T::AccountId = account("Origin", 0, 0); + let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); + let amount = 100u32.into(); + deposit_lp_tokens::(pool_currency_id, &origin, amount); + assert_ok!(T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount)); + + }: _(RawOrigin::Signed(origin), pool_currency_id, reward_currency_id) } impl_benchmark_test_suite!(Farming, crate::mock::ExtBuilder::build(), crate::mock::Test); diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 90e56b9e407..9fc79e05004 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -12,14 +12,14 @@ //! staked tokens for a reward schedule that distributed more than 0 tokens. //! //! The following design decisions have been made: -//! - The reward schedule is configured as a matrix such that a staked token (e.g., an AMM LP token) and an incentive -//! token (e.g., INTR or DOT) represent one reward schedule. This enables adding multiple reward currencies per staked -//! token. +//! - The reward schedule is configured as a matrix such that a staked token (e.g., an AMM LP token) +//! and an incentive token (e.g., INTR or DOT) represent one reward schedule. This enables adding +//! multiple reward currencies per staked token. //! - Rewards can be increased but not decreased unless the schedule is explicitly removed. //! - The rewards period cannot change without a migration. -//! - Only constant rewards per period are paid. To implement more complex reward schemes, the farming pallet relies on -//! the scheduler pallet. This allows a creator to configure different constant payouts by scheduling -//! `update_reward_schedule` in the future. +//! - Only constant rewards per period are paid. To implement more complex reward schemes, the +//! farming pallet relies on the scheduler pallet. This allows a creator to configure different +//! constant payouts by scheduling `update_reward_schedule` in the future. // #![deny(warnings)] #![cfg_attr(test, feature(proc_macro_hygiene))] @@ -37,89 +37,91 @@ mod mock; #[cfg(test)] mod tests; - -use codec::{Decode, Encode, MaxEncodedLen, FullCodec}; -use frame_support::{dispatch::DispatchResult, traits::Get, transactional, weights::Weight, PalletId, RuntimeDebug}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use core::fmt::Debug; +use frame_support::{ + dispatch::DispatchResult, traits::Get, transactional, weights::Weight, PalletId, RuntimeDebug, +}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use reward::RewardsApi; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AccountIdConversion, AtLeast32Bit, CheckedDiv, Saturating, Zero}, - ArithmeticError, DispatchError, + traits::{AccountIdConversion, AtLeast32Bit, CheckedDiv, Saturating, Zero}, + ArithmeticError, DispatchError, }; -use core::fmt::Debug; use sp_std::vec::Vec; pub use pallet::*; #[derive(Clone, Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct RewardSchedule { - /// Number of periods remaining - pub period_count: u32, - /// Amount of tokens to release - #[codec(compact)] - pub per_period: Balance, + /// Number of periods remaining + pub period_count: u32, + /// Amount of tokens to release + #[codec(compact)] + pub per_period: Balance, } impl RewardSchedule { - /// Returns total amount to distribute, `None` if calculation overflows - pub fn total(&self) -> Option { - self.per_period.checked_mul(&self.period_count.into()) - } - - /// Take the next reward and decrement the period count - pub fn take(&mut self) -> Option { - if self.period_count.gt(&0) { - self.period_count.saturating_dec(); - Some(self.per_period) - } else { - None - } - } + /// Returns total amount to distribute, `None` if calculation overflows + pub fn total(&self) -> Option { + self.per_period.checked_mul(&self.period_count.into()) + } + + /// Take the next reward and decrement the period count + pub fn take(&mut self) -> Option { + if self.period_count.gt(&0) { + self.period_count.saturating_dec(); + Some(self.per_period) + } else { + None + } + } } #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - pub(crate) type AccountIdOf = ::AccountId; + pub(crate) type AccountIdOf = ::AccountId; - pub(crate) type AssetIdOf = ::AssetId; + pub(crate) type AssetIdOf = ::AssetId; - pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; + pub(crate) type BalanceOf = + <::MultiCurrency as MultiCurrency>>::Balance; - pub(crate) type RewardScheduleOf = RewardSchedule>; + pub(crate) type RewardScheduleOf = RewardSchedule>; - /// ## Configuration - /// The pallet's configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// ## Configuration + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The farming pallet id, used for deriving pool accounts. - #[pallet::constant] - type FarmingPalletId: Get; + /// The farming pallet id, used for deriving pool accounts. + #[pallet::constant] + type FarmingPalletId: Get; - /// The treasury account id for funding pools. - #[pallet::constant] - type TreasuryAccountId: Get; + /// The treasury account id for funding pools. + #[pallet::constant] + type TreasuryAccountId: Get; - /// The period to accrue rewards. - #[pallet::constant] - type RewardPeriod: Get; + /// The period to accrue rewards. + #[pallet::constant] + type RewardPeriod: Get; - /// Reward pools to track stake. - type RewardPools: RewardsApi< - AssetIdOf, // pool id is the lp token - AccountIdOf, - BalanceOf, - CurrencyId = AssetIdOf, - >; + /// Reward pools to track stake. + type RewardPools: RewardsApi< + AssetIdOf, // pool id is the lp token + AccountIdOf, + BalanceOf, + CurrencyId = AssetIdOf, + >; - type AssetId: FullCodec + type AssetId: FullCodec + MaxEncodedLen + Eq + PartialEq @@ -133,118 +135,133 @@ pub mod pallet { + Into + Ord; - /// Currency handler to transfer tokens. - type MultiCurrency: MultiReservableCurrency< - AccountIdOf, - CurrencyId = Self::AssetId, - >; - - /// Weight information for the extrinsics. - type WeightInfo: WeightInfo; - } - - // The pallet's events - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { - RewardScheduleUpdated { - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - period_count: u32, - per_period: BalanceOf, - }, - RewardDistributed { - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - amount: BalanceOf, - }, - RewardClaimed { - account_id: AccountIdOf, - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - amount: BalanceOf, - }, - } - - #[pallet::error] - pub enum Error { - InsufficientStake, - } - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_initialize(now: T::BlockNumber) -> Weight { - if now % T::RewardPeriod::get() == Zero::zero() { - let mut count: u32 = 0; - // collect first to avoid modifying in-place - let schedules = RewardSchedules::::iter().collect::>(); - for (pool_currency_id, reward_currency_id, mut reward_schedule) in schedules.into_iter() { - if let Some(amount) = reward_schedule.take() { - if let Ok(_) = Self::try_distribute_reward(pool_currency_id, reward_currency_id, amount) { - // only update the schedule if we could distribute the reward - RewardSchedules::::insert(pool_currency_id, reward_currency_id, reward_schedule); - count.saturating_inc(); - Self::deposit_event(Event::RewardDistributed { - pool_currency_id, - reward_currency_id, - amount, - }); - } - } else { - // period count is zero - RewardSchedules::::remove(pool_currency_id, reward_currency_id); - // TODO: sweep leftover rewards? - } - } - T::WeightInfo::on_initialize(count) - } else { - Weight::zero() - } - } - } - - #[pallet::storage] - #[pallet::getter(fn reward_schedules)] - #[allow(clippy::disallowed_types)] - pub type RewardSchedules = StorageDoubleMap< - _, - Blake2_128Concat, - AssetIdOf, // lp token - Blake2_128Concat, - AssetIdOf, // reward currency - RewardScheduleOf, - ValueQuery, - >; - - #[pallet::pallet] - pub struct Pallet(_); - - // The pallet's dispatchable functions. - #[pallet::call] - impl Pallet { - /// Create or overwrite the reward schedule, if a reward schedule - /// already exists for the rewards currency the duration is added - /// to the existing duration and the rewards per period are modified - /// s.t. that the total (old remaining + new) rewards are distributed - /// over the new total duration - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::update_reward_schedule())] - #[transactional] - pub fn update_reward_schedule( - origin: OriginFor, - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - period_count: u32, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - ensure_root(origin)?; - // fund the pool account from treasury - let treasury_account_id = T::TreasuryAccountId::get(); - let pool_account_id = Self::pool_account_id(&pool_currency_id); - T::MultiCurrency::transfer(reward_currency_id, &treasury_account_id, &pool_account_id, amount)?; - - RewardSchedules::::try_mutate(pool_currency_id, reward_currency_id, |reward_schedule| { - let total_period_count = reward_schedule + /// Currency handler to transfer tokens. + type MultiCurrency: MultiReservableCurrency, CurrencyId = Self::AssetId>; + + /// Weight information for the extrinsics. + type WeightInfo: WeightInfo; + } + + // The pallet's events + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + RewardScheduleUpdated { + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + period_count: u32, + per_period: BalanceOf, + }, + RewardDistributed { + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + amount: BalanceOf, + }, + RewardClaimed { + account_id: AccountIdOf, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + amount: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + InsufficientStake, + } + + #[pallet::hooks] + impl Hooks for Pallet { + fn on_initialize(now: T::BlockNumber) -> Weight { + if now % T::RewardPeriod::get() == Zero::zero() { + let mut count: u32 = 0; + // collect first to avoid modifying in-place + let schedules = RewardSchedules::::iter().collect::>(); + for (pool_currency_id, reward_currency_id, mut reward_schedule) in + schedules.into_iter() + { + if let Some(amount) = reward_schedule.take() { + if let Ok(_) = Self::try_distribute_reward( + pool_currency_id, + reward_currency_id, + amount, + ) { + // only update the schedule if we could distribute the reward + RewardSchedules::::insert( + pool_currency_id, + reward_currency_id, + reward_schedule, + ); + count.saturating_inc(); + Self::deposit_event(Event::RewardDistributed { + pool_currency_id, + reward_currency_id, + amount, + }); + } + } else { + // period count is zero + RewardSchedules::::remove(pool_currency_id, reward_currency_id); + // TODO: sweep leftover rewards? + } + } + T::WeightInfo::on_initialize(count) + } else { + Weight::zero() + } + } + } + + #[pallet::storage] + #[pallet::getter(fn reward_schedules)] + #[allow(clippy::disallowed_types)] + pub type RewardSchedules = StorageDoubleMap< + _, + Blake2_128Concat, + AssetIdOf, // lp token + Blake2_128Concat, + AssetIdOf, // reward currency + RewardScheduleOf, + ValueQuery, + >; + + #[pallet::pallet] + pub struct Pallet(_); + + // The pallet's dispatchable functions. + #[pallet::call] + impl Pallet { + /// Create or overwrite the reward schedule, if a reward schedule + /// already exists for the rewards currency the duration is added + /// to the existing duration and the rewards per period are modified + /// s.t. that the total (old remaining + new) rewards are distributed + /// over the new total duration + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::update_reward_schedule())] + #[transactional] + pub fn update_reward_schedule( + origin: OriginFor, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + period_count: u32, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + ensure_root(origin)?; + // fund the pool account from treasury + let treasury_account_id = T::TreasuryAccountId::get(); + let pool_account_id = Self::pool_account_id(&pool_currency_id); + T::MultiCurrency::transfer( + reward_currency_id, + &treasury_account_id, + &pool_account_id, + amount, + )?; + + RewardSchedules::::try_mutate( + pool_currency_id, + reward_currency_id, + |reward_schedule| { + let total_period_count = reward_schedule .period_count .checked_add(period_count) .ok_or_else(|| { @@ -253,135 +270,142 @@ pub mod pallet { ); ArithmeticError::Overflow })?; - - let total_free = T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id); - let total_per_period = total_free.checked_div(&total_period_count.into()).unwrap_or_default(); - - reward_schedule.period_count = total_period_count; - reward_schedule.per_period = total_per_period; - - Self::deposit_event(Event::RewardScheduleUpdated { - pool_currency_id, - reward_currency_id, - period_count: total_period_count, - per_period: total_per_period, - }); - Ok(().into()) - }) - } - - /// Explicitly remove a reward schedule and transfer any remaining - /// balance to the treasury - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::remove_reward_schedule())] - #[transactional] - pub fn remove_reward_schedule( - origin: OriginFor, - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - // transfer unspent rewards to treasury - let treasury_account_id = T::TreasuryAccountId::get(); - let pool_account_id = Self::pool_account_id(&pool_currency_id); - T::MultiCurrency::transfer( - reward_currency_id, - &pool_account_id, - &treasury_account_id, - T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id), - )?; - - RewardSchedules::::remove(pool_currency_id, reward_currency_id); - Self::deposit_event(Event::RewardScheduleUpdated { - pool_currency_id, - reward_currency_id, - period_count: Zero::zero(), - per_period: Zero::zero(), - }); - - Ok(().into()) - } - - /// Stake the pool tokens in the reward pool - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::deposit())] - #[transactional] - pub fn deposit(origin: OriginFor, pool_currency_id: AssetIdOf) -> DispatchResult { - let who = ensure_signed(origin)?; - // reserve lp tokens to prevent spending - let amount = T::MultiCurrency::free_balance(pool_currency_id, &who); - T::MultiCurrency::reserve(pool_currency_id, &who, amount)?; - - // deposit lp tokens as stake - T::RewardPools::deposit_stake(&pool_currency_id, &who, amount) - } - - /// Unstake the pool tokens from the reward pool - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::withdraw())] - #[transactional] - pub fn withdraw( - origin: OriginFor, - pool_currency_id: AssetIdOf, - amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - // unreserve lp tokens to allow spending - let remaining = T::MultiCurrency::unreserve(pool_currency_id, &who, amount); - ensure!(remaining.is_zero(), Error::::InsufficientStake); - - // withdraw lp tokens from stake - T::RewardPools::withdraw_stake(&pool_currency_id, &who, amount) - } - - /// Withdraw any accrued rewards from the reward pool - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::claim())] - #[transactional] - pub fn claim( - origin: OriginFor, - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let pool_account_id = Self::pool_account_id(&pool_currency_id); - - // get reward from staking pool - let reward = T::RewardPools::withdraw_reward(&pool_currency_id, &who, reward_currency_id)?; - // transfer from pool to user - T::MultiCurrency::transfer(reward_currency_id, &pool_account_id, &who, reward)?; - - Self::deposit_event(Event::RewardClaimed { - account_id: who, - pool_currency_id, - reward_currency_id, - amount: reward, - }); - - Ok(()) - } - } + + let total_free = + T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id); + let total_per_period = + total_free.checked_div(&total_period_count.into()).unwrap_or_default(); + + reward_schedule.period_count = total_period_count; + reward_schedule.per_period = total_per_period; + + Self::deposit_event(Event::RewardScheduleUpdated { + pool_currency_id, + reward_currency_id, + period_count: total_period_count, + per_period: total_per_period, + }); + Ok(().into()) + }, + ) + } + + /// Explicitly remove a reward schedule and transfer any remaining + /// balance to the treasury + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::remove_reward_schedule())] + #[transactional] + pub fn remove_reward_schedule( + origin: OriginFor, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + // transfer unspent rewards to treasury + let treasury_account_id = T::TreasuryAccountId::get(); + let pool_account_id = Self::pool_account_id(&pool_currency_id); + T::MultiCurrency::transfer( + reward_currency_id, + &pool_account_id, + &treasury_account_id, + T::MultiCurrency::free_balance(reward_currency_id, &pool_account_id), + )?; + + RewardSchedules::::remove(pool_currency_id, reward_currency_id); + Self::deposit_event(Event::RewardScheduleUpdated { + pool_currency_id, + reward_currency_id, + period_count: Zero::zero(), + per_period: Zero::zero(), + }); + + Ok(().into()) + } + + /// Stake the pool tokens in the reward pool + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::deposit())] + #[transactional] + pub fn deposit(origin: OriginFor, pool_currency_id: AssetIdOf) -> DispatchResult { + let who = ensure_signed(origin)?; + // reserve lp tokens to prevent spending + let amount = T::MultiCurrency::free_balance(pool_currency_id, &who); + T::MultiCurrency::reserve(pool_currency_id, &who, amount)?; + + // deposit lp tokens as stake + T::RewardPools::deposit_stake(&pool_currency_id, &who, amount) + } + + /// Unstake the pool tokens from the reward pool + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::withdraw())] + #[transactional] + pub fn withdraw( + origin: OriginFor, + pool_currency_id: AssetIdOf, + amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + // unreserve lp tokens to allow spending + let remaining = T::MultiCurrency::unreserve(pool_currency_id, &who, amount); + ensure!(remaining.is_zero(), Error::::InsufficientStake); + + // withdraw lp tokens from stake + T::RewardPools::withdraw_stake(&pool_currency_id, &who, amount) + } + + /// Withdraw any accrued rewards from the reward pool + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::claim())] + #[transactional] + pub fn claim( + origin: OriginFor, + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let pool_account_id = Self::pool_account_id(&pool_currency_id); + + // get reward from staking pool + let reward = + T::RewardPools::withdraw_reward(&pool_currency_id, &who, reward_currency_id)?; + // transfer from pool to user + T::MultiCurrency::transfer(reward_currency_id, &pool_account_id, &who, reward)?; + + Self::deposit_event(Event::RewardClaimed { + account_id: who, + pool_currency_id, + reward_currency_id, + amount: reward, + }); + + Ok(()) + } + } } // "Internal" functions, callable by code. impl Pallet { - pub fn pool_account_id(pool_currency_id: &AssetIdOf) -> T::AccountId { - T::FarmingPalletId::get().into_sub_account_truncating(pool_currency_id) - } - - pub fn total_rewards(pool_currency_id: &AssetIdOf, reward_currency_id: &AssetIdOf) -> BalanceOf { - let pool_currency_id = pool_currency_id; - RewardSchedules::::get(pool_currency_id, reward_currency_id) - .total() - .unwrap_or_default() - } - - #[transactional] - fn try_distribute_reward( - pool_currency_id: AssetIdOf, - reward_currency_id: AssetIdOf, - amount: BalanceOf, - ) -> Result<(), DispatchError> { - T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount) - } + pub fn pool_account_id(pool_currency_id: &AssetIdOf) -> T::AccountId { + T::FarmingPalletId::get().into_sub_account_truncating(pool_currency_id) + } + + pub fn total_rewards( + pool_currency_id: &AssetIdOf, + reward_currency_id: &AssetIdOf, + ) -> BalanceOf { + let pool_currency_id = pool_currency_id; + RewardSchedules::::get(pool_currency_id, reward_currency_id) + .total() + .unwrap_or_default() + } + + #[transactional] + fn try_distribute_reward( + pool_currency_id: AssetIdOf, + reward_currency_id: AssetIdOf, + amount: BalanceOf, + ) -> Result<(), DispatchError> { + T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount) + } } diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs index 5416d55c5e0..87f8c10c099 100644 --- a/code/parachain/frame/farming/src/mock.rs +++ b/code/parachain/frame/farming/src/mock.rs @@ -1,16 +1,16 @@ use crate::{self as farming, Config, Error}; use frame_support::{ - parameter_types, - traits::{ConstU32, Everything}, - PalletId, + parameter_types, + traits::{ConstU32, Everything}, + PalletId, }; use orml_traits::parameter_type_with_key; // pub use primitives::currency::CurrencyId; use sp_arithmetic::FixedI128; use sp_core::H256; use sp_runtime::{ - generic::Header as GenericHeader, - traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + generic::Header as GenericHeader, + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, }; type Header = GenericHeader; @@ -20,21 +20,20 @@ type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Tokens: orml_tokens::{Pallet, Storage, /*Config,*/ Event}, - Rewards: reward::{Pallet, Call, Storage, Event}, - Farming: farming::{Pallet, Call, Storage, Event}, - } + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Tokens: orml_tokens::{Pallet, Storage, /*Config,*/ Event}, + Rewards: reward::{Pallet, Call, Storage, Event}, + Farming: farming::{Pallet, Call, Storage, Event}, + } ); pub type CurrencyId = u128; - pub type AccountId = u64; pub type Balance = u128; pub type BlockNumber = u128; @@ -42,89 +41,89 @@ pub type Index = u64; pub type SignedFixedPoint = FixedI128; parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; } impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = Index; - type BlockNumber = BlockNumber; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = Index; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { - // pub const GetNativeCurrencyId: CurrencyId = 1; - // pub const GetRelayChainCurrencyId: CurrencyId = 2; - // pub const GetWrappedCurrencyId: CurrencyId = 3; - pub const MaxLocks: u32 = 50; + // pub const GetNativeCurrencyId: CurrencyId = 1; + // pub const GetRelayChainCurrencyId: CurrencyId = 2; + // pub const GetWrappedCurrencyId: CurrencyId = 3; + pub const MaxLocks: u32 = 50; } parameter_type_with_key! { - pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { - 0 - }; + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + 0 + }; } impl orml_tokens::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Balance = Balance; - type Amount = i128; - type CurrencyId = CurrencyId; - type WeightInfo = (); - type ExistentialDeposits = ExistentialDeposits; - type CurrencyHooks = (); - type MaxLocks = MaxLocks; - type DustRemovalWhitelist = Everything; - type MaxReserves = ConstU32<0>; // we don't use named reserves - type ReserveIdentifier = (); // we don't use named reserves + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = i128; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = (); + type MaxLocks = MaxLocks; + type DustRemovalWhitelist = Everything; + type MaxReserves = ConstU32<0>; // we don't use named reserves + type ReserveIdentifier = (); // we don't use named reserves } impl reward::Config for Test { - type RuntimeEvent = RuntimeEvent; - type SignedFixedPoint = SignedFixedPoint; - type PoolId = CurrencyId; - type StakeId = AccountId; - type CurrencyId = CurrencyId; - // type GetNativeCurrencyId = GetNativeCurrencyId; - // type GetWrappedCurrencyId = GetWrappedCurrencyId; + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type StakeId = AccountId; + type CurrencyId = CurrencyId; + // type GetNativeCurrencyId = GetNativeCurrencyId; + // type GetWrappedCurrencyId = GetWrappedCurrencyId; } parameter_types! { - pub const FarmingPalletId: PalletId = PalletId(*b"farmings"); - pub TreasuryAccountId: AccountId = PalletId(*b"treasury").into_account_truncating(); - pub const RewardPeriod: BlockNumber = 10; + pub const FarmingPalletId: PalletId = PalletId(*b"farmings"); + pub TreasuryAccountId: AccountId = PalletId(*b"treasury").into_account_truncating(); + pub const RewardPeriod: BlockNumber = 10; } impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type FarmingPalletId = FarmingPalletId; - type TreasuryAccountId = TreasuryAccountId; - type RewardPeriod = RewardPeriod; - type RewardPools = Rewards; - type AssetId = CurrencyId; - type MultiCurrency = Tokens; - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type FarmingPalletId = FarmingPalletId; + type TreasuryAccountId = TreasuryAccountId; + type RewardPeriod = RewardPeriod; + type RewardPools = Rewards; + type AssetId = CurrencyId; + type MultiCurrency = Tokens; + type WeightInfo = (); } pub type TestEvent = RuntimeEvent; @@ -133,19 +132,19 @@ pub type TestError = Error; pub struct ExtBuilder; impl ExtBuilder { - pub fn build() -> sp_io::TestExternalities { - let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pub fn build() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - storage.into() - } + storage.into() + } } pub fn run_test(test: T) where - T: FnOnce(), + T: FnOnce(), { - ExtBuilder::build().execute_with(|| { - System::set_block_number(1); - test(); - }); + ExtBuilder::build().execute_with(|| { + System::set_block_number(1); + test(); + }); } diff --git a/code/parachain/frame/farming/src/tests.rs b/code/parachain/frame/farming/src/tests.rs index 2ffe55773c1..10cf67830ca 100644 --- a/code/parachain/frame/farming/src/tests.rs +++ b/code/parachain/frame/farming/src/tests.rs @@ -6,10 +6,10 @@ use orml_traits::MultiCurrency; type Event = crate::Event; macro_rules! assert_emitted { - ($event:expr) => { - let test_event = TestEvent::Farming($event); - assert!(System::events().iter().any(|a| a.event == test_event)); - }; + ($event:expr) => { + let test_event = TestEvent::Farming($event); + assert!(System::events().iter().any(|a| a.event == test_event)); + }; } // use primitives::CurrencyId; @@ -20,235 +20,214 @@ const REWARD_CURRENCY_ID: CurrencyId = 1; #[test] fn should_create_and_remove_reward_schedule() { - run_test(|| { - let reward_schedule = RewardSchedule { - period_count: 100, - per_period: 1000, - }; - let total_amount = reward_schedule.total().unwrap(); - - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - TreasuryAccountId::get(), - REWARD_CURRENCY_ID, - total_amount, - 0 - )); - - // creating a reward pool should transfer from treasury - assert_ok!(Farming::update_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - reward_schedule.period_count, - reward_schedule.total().unwrap(), - )); - - // check pool balance - assert_eq!( - Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), - total_amount - ); - - // deleting a reward pool should transfer back to treasury - assert_ok!(Farming::remove_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - )); - - // check treasury balance - assert_eq!( - Tokens::total_balance(REWARD_CURRENCY_ID, &TreasuryAccountId::get()), - total_amount - ); - - assert_emitted!(Event::RewardScheduleUpdated { - pool_currency_id: POOL_CURRENCY_ID, - reward_currency_id: REWARD_CURRENCY_ID, - period_count: 0, - per_period: 0, - }); - }) + run_test(|| { + let reward_schedule = RewardSchedule { period_count: 100, per_period: 1000 }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + // creating a reward pool should transfer from treasury + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // check pool balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + total_amount + ); + + // deleting a reward pool should transfer back to treasury + assert_ok!(Farming::remove_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + )); + + // check treasury balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &TreasuryAccountId::get()), + total_amount + ); + + assert_emitted!(Event::RewardScheduleUpdated { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + period_count: 0, + per_period: 0, + }); + }) } #[test] fn should_overwrite_existing_schedule() { - run_test(|| { - let reward_schedule_1 = RewardSchedule { - period_count: 200, - per_period: 20, - }; - let reward_schedule_2 = RewardSchedule { - period_count: 100, - per_period: 10, - }; - let total_amount = reward_schedule_1.total().unwrap() + reward_schedule_2.total().unwrap(); - let total_period_count = reward_schedule_1.period_count + reward_schedule_2.period_count; - let total_reward_per_period = total_amount / total_period_count as u128; - - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - TreasuryAccountId::get(), - REWARD_CURRENCY_ID, - total_amount, - 0 - )); - - // create first reward schedule - assert_ok!(Farming::update_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - reward_schedule_1.period_count, - reward_schedule_1.total().unwrap(), - )); - - // check pool balance - assert_eq!( - Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), - reward_schedule_1.total().unwrap(), - ); - - // overwrite second reward schedule - assert_ok!(Farming::update_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - reward_schedule_2.period_count, - reward_schedule_2.total().unwrap(), - )); - - // check pool balance now includes both - assert_eq!( - Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), - total_amount, - ); - - assert_emitted!(Event::RewardScheduleUpdated { - pool_currency_id: POOL_CURRENCY_ID, - reward_currency_id: REWARD_CURRENCY_ID, - period_count: total_period_count, - per_period: total_reward_per_period, - }); - }) + run_test(|| { + let reward_schedule_1 = RewardSchedule { period_count: 200, per_period: 20 }; + let reward_schedule_2 = RewardSchedule { period_count: 100, per_period: 10 }; + let total_amount = reward_schedule_1.total().unwrap() + reward_schedule_2.total().unwrap(); + let total_period_count = reward_schedule_1.period_count + reward_schedule_2.period_count; + let total_reward_per_period = total_amount / total_period_count as u128; + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + // create first reward schedule + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule_1.period_count, + reward_schedule_1.total().unwrap(), + )); + + // check pool balance + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + reward_schedule_1.total().unwrap(), + ); + + // overwrite second reward schedule + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule_2.period_count, + reward_schedule_2.total().unwrap(), + )); + + // check pool balance now includes both + assert_eq!( + Tokens::total_balance(REWARD_CURRENCY_ID, &Farming::pool_account_id(&POOL_CURRENCY_ID)), + total_amount, + ); + + assert_emitted!(Event::RewardScheduleUpdated { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + period_count: total_period_count, + per_period: total_reward_per_period, + }); + }) } fn mint_and_deposit(account_id: AccountId, amount: Balance) { - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - account_id, - POOL_CURRENCY_ID, - amount, - 0 - )); - - assert_ok!(Farming::deposit(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID,)); + assert_ok!(Tokens::set_balance(RuntimeOrigin::root(), account_id, POOL_CURRENCY_ID, amount, 0)); + + assert_ok!(Farming::deposit(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID,)); } #[test] fn should_deposit_and_withdraw_stake() { - run_test(|| { - let pool_tokens = 1000; - let account_id = 0; - - let reward_schedule = RewardSchedule { - period_count: 100, - per_period: 1000, - }; - let total_amount = reward_schedule.total().unwrap(); - - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - TreasuryAccountId::get(), - REWARD_CURRENCY_ID, - total_amount, - 0 - )); - - assert_ok!(Farming::update_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - reward_schedule.period_count, - reward_schedule.total().unwrap(), - )); - - // mint and deposit stake - mint_and_deposit(account_id, pool_tokens); - - // can't withdraw more stake than reserved - let withdraw_amount = pool_tokens * 2; - assert_err!( - Farming::withdraw(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID, withdraw_amount), - TestError::InsufficientStake - ); - - // only withdraw half of deposit - let withdraw_amount = pool_tokens / 2; - assert_ok!(Farming::withdraw( - RuntimeOrigin::signed(account_id), - POOL_CURRENCY_ID, - withdraw_amount - )); - assert_eq!(Tokens::free_balance(POOL_CURRENCY_ID, &account_id), withdraw_amount); - }) + run_test(|| { + let pool_tokens = 1000; + let account_id = 0; + + let reward_schedule = RewardSchedule { period_count: 100, per_period: 1000 }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // mint and deposit stake + mint_and_deposit(account_id, pool_tokens); + + // can't withdraw more stake than reserved + let withdraw_amount = pool_tokens * 2; + assert_err!( + Farming::withdraw(RuntimeOrigin::signed(account_id), POOL_CURRENCY_ID, withdraw_amount), + TestError::InsufficientStake + ); + + // only withdraw half of deposit + let withdraw_amount = pool_tokens / 2; + assert_ok!(Farming::withdraw( + RuntimeOrigin::signed(account_id), + POOL_CURRENCY_ID, + withdraw_amount + )); + assert_eq!(Tokens::free_balance(POOL_CURRENCY_ID, &account_id), withdraw_amount); + }) } #[test] fn should_deposit_stake_and_claim_reward() { - run_test(|| { - let pool_tokens = 1000; - let account_id = 0; - - // setup basic reward schedule - let reward_schedule = RewardSchedule { - period_count: 100, - per_period: 1000, - }; - let total_amount = reward_schedule.total().unwrap(); - - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - TreasuryAccountId::get(), - REWARD_CURRENCY_ID, - total_amount, - 0 - )); - - assert_ok!(Farming::update_reward_schedule( - RuntimeOrigin::root(), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - reward_schedule.period_count, - reward_schedule.total().unwrap(), - )); - - // mint and deposit stake - mint_and_deposit(account_id, pool_tokens); - - // check that we distribute per period - Farming::on_initialize(10); - assert_emitted!(Event::RewardDistributed { - pool_currency_id: POOL_CURRENCY_ID, - reward_currency_id: REWARD_CURRENCY_ID, - amount: reward_schedule.per_period, - }); - assert_eq!( - RewardSchedules::::get(POOL_CURRENCY_ID, REWARD_CURRENCY_ID).period_count, - reward_schedule.period_count - 1 - ); - - // withdraw reward - assert_ok!(Farming::claim( - RuntimeOrigin::signed(account_id), - POOL_CURRENCY_ID, - REWARD_CURRENCY_ID, - )); - // only one account with stake so they get all rewards - assert_eq!( - Tokens::free_balance(REWARD_CURRENCY_ID, &account_id), - reward_schedule.per_period - ); - }) + run_test(|| { + let pool_tokens = 1000; + let account_id = 0; + + // setup basic reward schedule + let reward_schedule = RewardSchedule { period_count: 100, per_period: 1000 }; + let total_amount = reward_schedule.total().unwrap(); + + assert_ok!(Tokens::set_balance( + RuntimeOrigin::root(), + TreasuryAccountId::get(), + REWARD_CURRENCY_ID, + total_amount, + 0 + )); + + assert_ok!(Farming::update_reward_schedule( + RuntimeOrigin::root(), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + reward_schedule.period_count, + reward_schedule.total().unwrap(), + )); + + // mint and deposit stake + mint_and_deposit(account_id, pool_tokens); + + // check that we distribute per period + Farming::on_initialize(10); + assert_emitted!(Event::RewardDistributed { + pool_currency_id: POOL_CURRENCY_ID, + reward_currency_id: REWARD_CURRENCY_ID, + amount: reward_schedule.per_period, + }); + assert_eq!( + RewardSchedules::::get(POOL_CURRENCY_ID, REWARD_CURRENCY_ID).period_count, + reward_schedule.period_count - 1 + ); + + // withdraw reward + assert_ok!(Farming::claim( + RuntimeOrigin::signed(account_id), + POOL_CURRENCY_ID, + REWARD_CURRENCY_ID, + )); + // only one account with stake so they get all rewards + assert_eq!( + Tokens::free_balance(REWARD_CURRENCY_ID, &account_id), + reward_schedule.per_period + ); + }) } diff --git a/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs index 2ac3b08ed85..57679f118ee 100644 --- a/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs +++ b/code/parachain/frame/reward/rpc/runtime-api/src/lib.rs @@ -15,37 +15,41 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// a wrapper around a balance, used in RPC to workaround a bug where using u128 /// in runtime-apis fails. See pub struct BalanceWrapper { - #[cfg_attr(feature = "std", serde(bound(serialize = "T: std::fmt::Display")))] - #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] - #[cfg_attr(feature = "std", serde(bound(deserialize = "T: std::str::FromStr")))] - #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] - pub amount: T, + #[cfg_attr(feature = "std", serde(bound(serialize = "T: std::fmt::Display")))] + #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] + #[cfg_attr(feature = "std", serde(bound(deserialize = "T: std::str::FromStr")))] + #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] + pub amount: T, } #[cfg(feature = "std")] -fn serialize_as_string(t: &T, serializer: S) -> Result { - serializer.serialize_str(&t.to_string()) +fn serialize_as_string( + t: &T, + serializer: S, +) -> Result { + serializer.serialize_str(&t.to_string()) } #[cfg(feature = "std")] -fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - s.parse::() - .map_err(|_| serde::de::Error::custom("Parse from string failed")) +fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>( + deserializer: D, +) -> Result { + let s = String::deserialize(deserializer)?; + s.parse::().map_err(|_| serde::de::Error::custom("Parse from string failed")) } sp_api::decl_runtime_apis! { - pub trait RewardApi where - AccountId: Codec, - CurrencyId: Codec, - Balance: Codec, - BlockNumber: Codec, - UnsignedFixedPoint: Codec, - { - /// Calculate the number of farming rewards accrued - fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; - - /// Estimate farming rewards for remaining incentives - fn estimate_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; - } + pub trait RewardApi where + AccountId: Codec, + CurrencyId: Codec, + Balance: Codec, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, + { + /// Calculate the number of farming rewards accrued + fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; + + /// Estimate farming rewards for remaining incentives + fn estimate_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError>; + } } diff --git a/code/parachain/frame/reward/rpc/src/lib.rs b/code/parachain/frame/reward/rpc/src/lib.rs index d7fb22b3e35..aa2150544a8 100644 --- a/code/parachain/frame/reward/rpc/src/lib.rs +++ b/code/parachain/frame/reward/rpc/src/lib.rs @@ -2,17 +2,17 @@ use codec::Codec; use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, - proc_macros::rpc, - types::error::{CallError, ErrorCode, ErrorObject}, + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, }; use reward_rpc_runtime_api::BalanceWrapper; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, - DispatchError, + generic::BlockId, + traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, + DispatchError, }; use std::sync::Arc; @@ -21,104 +21,111 @@ pub use reward_rpc_runtime_api::RewardApi as RewardRuntimeApi; #[rpc(client, server)] pub trait RewardApi where - Balance: Codec + MaybeDisplay + MaybeFromStr, - AccountId: Codec, - CurrencyId: Codec, - BlockNumber: Codec, - UnsignedFixedPoint: Codec, + Balance: Codec + MaybeDisplay + MaybeFromStr, + AccountId: Codec, + CurrencyId: Codec, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, { - #[method(name = "reward_computeFarmingReward")] - fn compute_farming_reward( - &self, - account_id: AccountId, - pool_currency_id: CurrencyId, - reward_currency_id: CurrencyId, - at: Option, - ) -> RpcResult>; + #[method(name = "reward_computeFarmingReward")] + fn compute_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; - #[method(name = "reward_estimateFarmingReward")] - fn estimate_farming_reward( - &self, - account_id: AccountId, - pool_currency_id: CurrencyId, - reward_currency_id: CurrencyId, - at: Option, - ) -> RpcResult>; + #[method(name = "reward_estimateFarmingReward")] + fn estimate_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option, + ) -> RpcResult>; } fn internal_err(message: T) -> JsonRpseeError { - JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - ErrorCode::InternalError.code(), - message.to_string(), - None::<()>, - ))) + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + message.to_string(), + None::<()>, + ))) } /// A struct that implements the [`RewardApi`]. pub struct Reward { - client: Arc, - _marker: std::marker::PhantomData, + client: Arc, + _marker: std::marker::PhantomData, } impl Reward { - /// Create new `Reward` with the given reference to the client. - pub fn new(client: Arc) -> Self { - Reward { - client, - _marker: Default::default(), - } - } + /// Create new `Reward` with the given reference to the client. + pub fn new(client: Arc) -> Self { + Reward { client, _marker: Default::default() } + } } -fn handle_response(result: Result, E>, msg: String) -> RpcResult { - result - .map_err(|err| internal_err(format!("Runtime error: {:?}: {:?}", msg, err)))? - .map_err(|err| internal_err(format!("Execution error: {:?}: {:?}", msg, err))) +fn handle_response( + result: Result, E>, + msg: String, +) -> RpcResult { + result + .map_err(|err| internal_err(format!("Runtime error: {:?}: {:?}", msg, err)))? + .map_err(|err| internal_err(format!("Execution error: {:?}: {:?}", msg, err))) } #[async_trait] impl - RewardApiServer<::Hash, AccountId, CurrencyId, Balance, BlockNumber, UnsignedFixedPoint> - for Reward + RewardApiServer< + ::Hash, + AccountId, + CurrencyId, + Balance, + BlockNumber, + UnsignedFixedPoint, + > for Reward where - Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: RewardRuntimeApi, - AccountId: Codec, - CurrencyId: Codec, - Balance: Codec + MaybeDisplay + MaybeFromStr, - BlockNumber: Codec, - UnsignedFixedPoint: Codec, + Block: BlockT, + C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + C::Api: + RewardRuntimeApi, + AccountId: Codec, + CurrencyId: Codec, + Balance: Codec + MaybeDisplay + MaybeFromStr, + BlockNumber: Codec, + UnsignedFixedPoint: Codec, { - fn compute_farming_reward( - &self, - account_id: AccountId, - pool_currency_id: CurrencyId, - reward_currency_id: CurrencyId, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + fn compute_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - handle_response( - api.compute_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), - "Unable to compute the current reward".into(), - ) - } + handle_response( + api.compute_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), + "Unable to compute the current reward".into(), + ) + } - fn estimate_farming_reward( - &self, - account_id: AccountId, - pool_currency_id: CurrencyId, - reward_currency_id: CurrencyId, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + fn estimate_farming_reward( + &self, + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); - handle_response( - api.estimate_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), - "Unable to estimate the current reward".into(), - ) - } + handle_response( + api.estimate_farming_reward(&at, account_id, pool_currency_id, reward_currency_id), + "Unable to estimate the current reward".into(), + ) + } } diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index 03348ef6128..f34b1e0b74e 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -13,15 +13,17 @@ mod tests; use codec::{Decode, Encode, EncodeLike}; use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - ensure + dispatch::{DispatchError, DispatchResult}, + ensure, }; use scale_info::TypeInfo; use sp_arithmetic::FixedPointNumber; use sp_runtime::{ - traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, MaybeSerializeDeserialize, Saturating, Zero}, - ArithmeticError, FixedI128, FixedU128, + traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, MaybeSerializeDeserialize, Saturating, Zero, + }, + ArithmeticError, FixedI128, FixedU128, }; use sp_std::{cmp::PartialOrd, collections::btree_set::BTreeSet, convert::TryInto, fmt::Debug}; @@ -30,516 +32,550 @@ pub(crate) type SignedFixedPoint = >::SignedFixedPoint pub use pallet::*; pub trait BalanceToFixedPoint { - fn to_fixed(self) -> Option; + fn to_fixed(self) -> Option; } impl BalanceToFixedPoint for u128 { - fn to_fixed(self) -> Option { - FixedI128::checked_from_integer( - TryInto::<::Inner>::try_into(self).ok()?, - ) - } + fn to_fixed(self) -> Option { + FixedI128::checked_from_integer( + TryInto::<::Inner>::try_into(self).ok()?, + ) + } } pub trait TruncateFixedPointToInt: FixedPointNumber { - /// take a fixed point number and turns it into the truncated inner representation, - /// e.g. FixedU128(1.23) -> 1u128 - fn truncate_to_inner(&self) -> Option<::Inner>; + /// take a fixed point number and turns it into the truncated inner representation, + /// e.g. FixedU128(1.23) -> 1u128 + fn truncate_to_inner(&self) -> Option<::Inner>; } impl TruncateFixedPointToInt for FixedI128 { - fn truncate_to_inner(&self) -> Option { - self.into_inner().checked_div(FixedI128::accuracy()) - } + fn truncate_to_inner(&self) -> Option { + self.into_inner().checked_div(FixedI128::accuracy()) + } } impl TruncateFixedPointToInt for FixedU128 { - fn truncate_to_inner(&self) -> Option<::Inner> { - self.into_inner().checked_div(FixedU128::accuracy()) - } + fn truncate_to_inner(&self) -> Option<::Inner> { + self.into_inner().checked_div(FixedU128::accuracy()) + } } #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - - /// ## Configuration - /// The pallet's configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Signed fixed point type. - type SignedFixedPoint: FixedPointNumber + TruncateFixedPointToInt + Encode + EncodeLike + Decode + TypeInfo; - - /// The pool identifier type. - type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; - - /// The stake identifier type. - type StakeId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; - - /// The currency ID type. - type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; - - // #[pallet::constant] - // type GetNativeCurrencyId: Get; - - // #[pallet::constant] - // type GetWrappedCurrencyId: Get; - } - - // The pallet's events - #[pallet::event] - #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event, I: 'static = ()> { - DepositStake { - pool_id: T::PoolId, - stake_id: T::StakeId, - amount: T::SignedFixedPoint, - }, - DistributeReward { - currency_id: T::CurrencyId, - amount: T::SignedFixedPoint, - }, - WithdrawStake { - pool_id: T::PoolId, - stake_id: T::StakeId, - amount: T::SignedFixedPoint, - }, - WithdrawReward { - pool_id: T::PoolId, - stake_id: T::StakeId, - currency_id: T::CurrencyId, - amount: T::SignedFixedPoint, - }, - } - - #[pallet::error] - pub enum Error { - /// Unable to convert value. - TryIntoIntError, - /// Balance not sufficient to withdraw stake. - InsufficientFunds, - /// Cannot distribute rewards without stake. - ZeroTotalStake, - } - - #[pallet::hooks] - impl, I: 'static> Hooks for Pallet { } - - /// The total stake deposited to this reward pool. - #[pallet::storage] - #[pallet::getter(fn total_stake)] - #[allow(clippy::disallowed_types)] - pub type TotalStake, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::PoolId, SignedFixedPoint, ValueQuery>; - - /// The total unclaimed rewards distributed to this reward pool. - /// NOTE: this is currently only used for integration tests. - #[pallet::storage] - #[pallet::getter(fn total_rewards)] - #[allow(clippy::disallowed_types)] - pub type TotalRewards, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::CurrencyId, SignedFixedPoint, ValueQuery>; - - /// Used to compute the rewards for a participant's stake. - #[pallet::storage] - #[pallet::getter(fn reward_per_token)] - #[allow(clippy::disallowed_types)] - pub type RewardPerToken, I: 'static = ()> = StorageDoubleMap< - _, - Blake2_128Concat, - T::CurrencyId, - Blake2_128Concat, - T::PoolId, - SignedFixedPoint, - ValueQuery, - >; - - /// The stake of a participant in this reward pool. - #[pallet::storage] - #[allow(clippy::disallowed_types)] - pub type Stake, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, (T::PoolId, T::StakeId), SignedFixedPoint, ValueQuery>; - - /// Accounts for previous changes in stake size. - #[pallet::storage] - #[allow(clippy::disallowed_types)] - pub type RewardTally, I: 'static = ()> = StorageDoubleMap< - _, - Blake2_128Concat, - T::CurrencyId, - Blake2_128Concat, - (T::PoolId, T::StakeId), - SignedFixedPoint, - ValueQuery, - >; - - /// Track the currencies used for rewards. - #[pallet::storage] - #[allow(clippy::disallowed_types)] - pub type RewardCurrencies, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::PoolId, BTreeSet, ValueQuery>; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - // The pallet's dispatchable functions. - #[pallet::call] - impl, I: 'static> Pallet {} + use super::*; + use frame_support::pallet_prelude::*; + + /// ## Configuration + /// The pallet's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Signed fixed point type. + type SignedFixedPoint: FixedPointNumber + + TruncateFixedPointToInt + + Encode + + EncodeLike + + Decode + + TypeInfo; + + /// The pool identifier type. + type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The stake identifier type. + type StakeId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The currency ID type. + type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; + + // #[pallet::constant] + // type GetNativeCurrencyId: Get; + + // #[pallet::constant] + // type GetWrappedCurrencyId: Get; + } + + // The pallet's events + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event, I: 'static = ()> { + DepositStake { + pool_id: T::PoolId, + stake_id: T::StakeId, + amount: T::SignedFixedPoint, + }, + DistributeReward { + currency_id: T::CurrencyId, + amount: T::SignedFixedPoint, + }, + WithdrawStake { + pool_id: T::PoolId, + stake_id: T::StakeId, + amount: T::SignedFixedPoint, + }, + WithdrawReward { + pool_id: T::PoolId, + stake_id: T::StakeId, + currency_id: T::CurrencyId, + amount: T::SignedFixedPoint, + }, + } + + #[pallet::error] + pub enum Error { + /// Unable to convert value. + TryIntoIntError, + /// Balance not sufficient to withdraw stake. + InsufficientFunds, + /// Cannot distribute rewards without stake. + ZeroTotalStake, + } + + #[pallet::hooks] + impl, I: 'static> Hooks for Pallet {} + + /// The total stake deposited to this reward pool. + #[pallet::storage] + #[pallet::getter(fn total_stake)] + #[allow(clippy::disallowed_types)] + pub type TotalStake, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::PoolId, SignedFixedPoint, ValueQuery>; + + /// The total unclaimed rewards distributed to this reward pool. + /// NOTE: this is currently only used for integration tests. + #[pallet::storage] + #[pallet::getter(fn total_rewards)] + #[allow(clippy::disallowed_types)] + pub type TotalRewards, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CurrencyId, SignedFixedPoint, ValueQuery>; + + /// Used to compute the rewards for a participant's stake. + #[pallet::storage] + #[pallet::getter(fn reward_per_token)] + #[allow(clippy::disallowed_types)] + pub type RewardPerToken, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CurrencyId, + Blake2_128Concat, + T::PoolId, + SignedFixedPoint, + ValueQuery, + >; + + /// The stake of a participant in this reward pool. + #[pallet::storage] + #[allow(clippy::disallowed_types)] + pub type Stake, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + (T::PoolId, T::StakeId), + SignedFixedPoint, + ValueQuery, + >; + + /// Accounts for previous changes in stake size. + #[pallet::storage] + #[allow(clippy::disallowed_types)] + pub type RewardTally, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CurrencyId, + Blake2_128Concat, + (T::PoolId, T::StakeId), + SignedFixedPoint, + ValueQuery, + >; + + /// Track the currencies used for rewards. + #[pallet::storage] + #[allow(clippy::disallowed_types)] + pub type RewardCurrencies, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::PoolId, BTreeSet, ValueQuery>; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + // The pallet's dispatchable functions. + #[pallet::call] + impl, I: 'static> Pallet {} } #[macro_export] macro_rules! checked_add_mut { - ($storage:ty, $amount:expr) => { - <$storage>::mutate(|value| { - *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; - Ok::<_, DispatchError>(()) - })?; - }; - ($storage:ty, $currency:expr, $amount:expr) => { - <$storage>::mutate($currency, |value| { - *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; - Ok::<_, DispatchError>(()) - })?; - }; - ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { - <$storage>::mutate($currency, $account, |value| { - *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; - Ok::<_, DispatchError>(()) - })?; - }; + ($storage:ty, $amount:expr) => { + <$storage>::mutate(|value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $amount:expr) => { + <$storage>::mutate($currency, |value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { + <$storage>::mutate($currency, $account, |value| { + *value = value.checked_add($amount).ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + }; } macro_rules! checked_sub_mut { - ($storage:ty, $amount:expr) => { - <$storage>::mutate(|value| { - *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; - Ok::<_, DispatchError>(()) - })?; - }; - ($storage:ty, $currency:expr, $amount:expr) => { - <$storage>::mutate($currency, |value| { - *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; - Ok::<_, DispatchError>(()) - })?; - }; - ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { - <$storage>::mutate($currency, $account, |value| { - *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; - Ok::<_, DispatchError>(()) - })?; - }; + ($storage:ty, $amount:expr) => { + <$storage>::mutate(|value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $amount:expr) => { + <$storage>::mutate($currency, |value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; + ($storage:ty, $currency:expr, $account:expr, $amount:expr) => { + <$storage>::mutate($currency, $account, |value| { + *value = value.checked_sub($amount).ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + }; } // "Internal" functions, callable by code. impl, I: 'static> Pallet { - pub fn stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> SignedFixedPoint { - Stake::::get((pool_id, stake_id)) - } - - pub fn get_total_rewards( - currency_id: T::CurrencyId, - ) -> Result<::Inner, DispatchError> { - Ok(Self::total_rewards(currency_id) - .truncate_to_inner() - .ok_or(Error::::TryIntoIntError)?) - } - - pub fn deposit_stake( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - amount: SignedFixedPoint, - ) -> Result<(), DispatchError> { - checked_add_mut!(Stake, (pool_id, stake_id), &amount); - checked_add_mut!(TotalStake, pool_id, &amount); - - for currency_id in RewardCurrencies::::get(pool_id) { - >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { - let reward_per_token = Self::reward_per_token(currency_id, pool_id); - let reward_per_token_mul_amount = - reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; - *reward_tally = reward_tally - .checked_add(&reward_per_token_mul_amount) - .ok_or(ArithmeticError::Overflow)?; - Ok::<_, DispatchError>(()) - })?; - } - - Self::deposit_event(Event::::DepositStake { - pool_id: pool_id.clone(), - stake_id: stake_id.clone(), - amount, - }); - - Ok(()) - } - - pub fn distribute_reward( - pool_id: &T::PoolId, - currency_id: T::CurrencyId, - reward: SignedFixedPoint, - ) -> DispatchResult { - if reward.is_zero() { - return Ok(()); - } - let total_stake = Self::total_stake(pool_id); - ensure!(!total_stake.is_zero(), Error::::ZeroTotalStake); - - // track currency for future deposits / withdrawals - RewardCurrencies::::mutate(pool_id, |reward_currencies| { - reward_currencies.insert(currency_id); - }); - - let reward_div_total_stake = reward.checked_div(&total_stake).ok_or(ArithmeticError::Underflow)?; - checked_add_mut!(RewardPerToken, currency_id, pool_id, &reward_div_total_stake); - checked_add_mut!(TotalRewards, currency_id, &reward); - - Self::deposit_event(Event::::DistributeReward { - currency_id, - amount: reward, - }); - Ok(()) - } - - pub fn compute_reward( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - currency_id: T::CurrencyId, - ) -> Result< as FixedPointNumber>::Inner, DispatchError> { - let stake = Self::stake(pool_id, stake_id); - let reward_per_token = Self::reward_per_token(currency_id, pool_id); - // FIXME: this can easily overflow with large numbers - let stake_mul_reward_per_token = stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?; - let reward_tally = >::get(currency_id, (pool_id, stake_id)); - // TODO: this can probably be saturated - let reward = stake_mul_reward_per_token - .checked_sub(&reward_tally) - .ok_or(ArithmeticError::Underflow)? - .truncate_to_inner() - .ok_or(Error::::TryIntoIntError)?; - Ok(reward) - } - - pub fn withdraw_stake( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - amount: SignedFixedPoint, - ) -> Result<(), DispatchError> { - if amount > Self::stake(pool_id, stake_id) { - return Err(Error::::InsufficientFunds.into()); - } - - checked_sub_mut!(Stake, (pool_id, stake_id), &amount); - checked_sub_mut!(TotalStake, pool_id, &amount); - - for currency_id in RewardCurrencies::::get(pool_id) { - >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { - let reward_per_token = Self::reward_per_token(currency_id, pool_id); - let reward_per_token_mul_amount = - reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; - - *reward_tally = reward_tally - .checked_sub(&reward_per_token_mul_amount) - .ok_or(ArithmeticError::Underflow)?; - Ok::<_, DispatchError>(()) - })?; - } - - Self::deposit_event(Event::::WithdrawStake { - pool_id: pool_id.clone(), - stake_id: stake_id.clone(), - amount, - }); - Ok(()) - } - - pub fn withdraw_reward( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - currency_id: T::CurrencyId, - ) -> Result< as FixedPointNumber>::Inner, DispatchError> { - let reward = Self::compute_reward(pool_id, stake_id, currency_id)?; - let reward_as_fixed = - SignedFixedPoint::::checked_from_integer(reward).ok_or(Error::::TryIntoIntError)?; - checked_sub_mut!(TotalRewards, currency_id, &reward_as_fixed); - - let stake = Self::stake(pool_id, stake_id); - let reward_per_token = Self::reward_per_token(currency_id, pool_id); - >::insert( - currency_id, - (pool_id, stake_id), - stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?, - ); - - Self::deposit_event(Event::::WithdrawReward { - currency_id, - pool_id: pool_id.clone(), - stake_id: stake_id.clone(), - amount: reward_as_fixed, - }); - Ok(reward) - } + pub fn stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> SignedFixedPoint { + Stake::::get((pool_id, stake_id)) + } + + pub fn get_total_rewards( + currency_id: T::CurrencyId, + ) -> Result<::Inner, DispatchError> { + Ok(Self::total_rewards(currency_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)?) + } + + pub fn deposit_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: SignedFixedPoint, + ) -> Result<(), DispatchError> { + checked_add_mut!(Stake, (pool_id, stake_id), &amount); + checked_add_mut!(TotalStake, pool_id, &amount); + + for currency_id in RewardCurrencies::::get(pool_id) { + >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + let reward_per_token_mul_amount = + reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; + *reward_tally = reward_tally + .checked_add(&reward_per_token_mul_amount) + .ok_or(ArithmeticError::Overflow)?; + Ok::<_, DispatchError>(()) + })?; + } + + Self::deposit_event(Event::::DepositStake { + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount, + }); + + Ok(()) + } + + pub fn distribute_reward( + pool_id: &T::PoolId, + currency_id: T::CurrencyId, + reward: SignedFixedPoint, + ) -> DispatchResult { + if reward.is_zero() { + return Ok(()) + } + let total_stake = Self::total_stake(pool_id); + ensure!(!total_stake.is_zero(), Error::::ZeroTotalStake); + + // track currency for future deposits / withdrawals + RewardCurrencies::::mutate(pool_id, |reward_currencies| { + reward_currencies.insert(currency_id); + }); + + let reward_div_total_stake = + reward.checked_div(&total_stake).ok_or(ArithmeticError::Underflow)?; + checked_add_mut!(RewardPerToken, currency_id, pool_id, &reward_div_total_stake); + checked_add_mut!(TotalRewards, currency_id, &reward); + + Self::deposit_event(Event::::DistributeReward { currency_id, amount: reward }); + Ok(()) + } + + pub fn compute_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result< as FixedPointNumber>::Inner, DispatchError> { + let stake = Self::stake(pool_id, stake_id); + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + // FIXME: this can easily overflow with large numbers + let stake_mul_reward_per_token = + stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?; + let reward_tally = >::get(currency_id, (pool_id, stake_id)); + // TODO: this can probably be saturated + let reward = stake_mul_reward_per_token + .checked_sub(&reward_tally) + .ok_or(ArithmeticError::Underflow)? + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)?; + Ok(reward) + } + + pub fn withdraw_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: SignedFixedPoint, + ) -> Result<(), DispatchError> { + if amount > Self::stake(pool_id, stake_id) { + return Err(Error::::InsufficientFunds.into()) + } + + checked_sub_mut!(Stake, (pool_id, stake_id), &amount); + checked_sub_mut!(TotalStake, pool_id, &amount); + + for currency_id in RewardCurrencies::::get(pool_id) { + >::mutate(currency_id, (pool_id, stake_id), |reward_tally| { + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + let reward_per_token_mul_amount = + reward_per_token.checked_mul(&amount).ok_or(ArithmeticError::Overflow)?; + + *reward_tally = reward_tally + .checked_sub(&reward_per_token_mul_amount) + .ok_or(ArithmeticError::Underflow)?; + Ok::<_, DispatchError>(()) + })?; + } + + Self::deposit_event(Event::::WithdrawStake { + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount, + }); + Ok(()) + } + + pub fn withdraw_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result< as FixedPointNumber>::Inner, DispatchError> { + let reward = Self::compute_reward(pool_id, stake_id, currency_id)?; + let reward_as_fixed = SignedFixedPoint::::checked_from_integer(reward) + .ok_or(Error::::TryIntoIntError)?; + checked_sub_mut!(TotalRewards, currency_id, &reward_as_fixed); + + let stake = Self::stake(pool_id, stake_id); + let reward_per_token = Self::reward_per_token(currency_id, pool_id); + >::insert( + currency_id, + (pool_id, stake_id), + stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?, + ); + + Self::deposit_event(Event::::WithdrawReward { + currency_id, + pool_id: pool_id.clone(), + stake_id: stake_id.clone(), + amount: reward_as_fixed, + }); + Ok(reward) + } } pub trait RewardsApi where - Balance: Saturating + PartialOrd, + Balance: Saturating + PartialOrd, { - type CurrencyId; - - /// Distribute the `amount` to all participants OR error if zero total stake. - fn distribute_reward(pool_id: &PoolId, currency_id: Self::CurrencyId, amount: Balance) -> DispatchResult; - - /// Compute the expected reward for the `stake_id`. - fn compute_reward( - pool_id: &PoolId, - stake_id: &StakeId, - currency_id: Self::CurrencyId, - ) -> Result; - - /// Withdraw all rewards from the `stake_id`. - fn withdraw_reward( - pool_id: &PoolId, - stake_id: &StakeId, - currency_id: Self::CurrencyId, - ) -> Result; - - /// Deposit stake for an account. - fn deposit_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; - - /// Withdraw stake for an account. - fn withdraw_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; - - /// Withdraw all stake for an account. - fn withdraw_all_stake(pool_id: &PoolId, stake_id: &StakeId) -> DispatchResult { - Self::withdraw_stake(pool_id, stake_id, Self::get_stake(pool_id, stake_id)?) - } - - /// Return the stake associated with the `pool_id`. - fn get_total_stake(pool_id: &PoolId) -> Result; - - /// Return the stake associated with the `stake_id`. - fn get_stake(pool_id: &PoolId, stake_id: &StakeId) -> Result; - - /// Set the stake to `amount` for `stake_id` regardless of its current stake. - fn set_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult { - let current_stake = Self::get_stake(pool_id, stake_id)?; - if current_stake < amount { - let additional_stake = amount.saturating_sub(current_stake); - Self::deposit_stake(pool_id, stake_id, additional_stake) - } else if current_stake > amount { - let surplus_stake = current_stake.saturating_sub(amount); - Self::withdraw_stake(pool_id, stake_id, surplus_stake) - } else { - Ok(()) - } - } + type CurrencyId; + + /// Distribute the `amount` to all participants OR error if zero total stake. + fn distribute_reward( + pool_id: &PoolId, + currency_id: Self::CurrencyId, + amount: Balance, + ) -> DispatchResult; + + /// Compute the expected reward for the `stake_id`. + fn compute_reward( + pool_id: &PoolId, + stake_id: &StakeId, + currency_id: Self::CurrencyId, + ) -> Result; + + /// Withdraw all rewards from the `stake_id`. + fn withdraw_reward( + pool_id: &PoolId, + stake_id: &StakeId, + currency_id: Self::CurrencyId, + ) -> Result; + + /// Deposit stake for an account. + fn deposit_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; + + /// Withdraw stake for an account. + fn withdraw_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult; + + /// Withdraw all stake for an account. + fn withdraw_all_stake(pool_id: &PoolId, stake_id: &StakeId) -> DispatchResult { + Self::withdraw_stake(pool_id, stake_id, Self::get_stake(pool_id, stake_id)?) + } + + /// Return the stake associated with the `pool_id`. + fn get_total_stake(pool_id: &PoolId) -> Result; + + /// Return the stake associated with the `stake_id`. + fn get_stake(pool_id: &PoolId, stake_id: &StakeId) -> Result; + + /// Set the stake to `amount` for `stake_id` regardless of its current stake. + fn set_stake(pool_id: &PoolId, stake_id: &StakeId, amount: Balance) -> DispatchResult { + let current_stake = Self::get_stake(pool_id, stake_id)?; + if current_stake < amount { + let additional_stake = amount.saturating_sub(current_stake); + Self::deposit_stake(pool_id, stake_id, additional_stake) + } else if current_stake > amount { + let surplus_stake = current_stake.saturating_sub(amount); + Self::withdraw_stake(pool_id, stake_id, surplus_stake) + } else { + Ok(()) + } + } } impl RewardsApi for Pallet where - T: Config, - I: 'static, - Balance: BalanceToFixedPoint> + Saturating + PartialOrd, - ::Inner: TryInto, + T: Config, + I: 'static, + Balance: BalanceToFixedPoint> + Saturating + PartialOrd, + ::Inner: TryInto, { - type CurrencyId = T::CurrencyId; - - fn distribute_reward(pool_id: &T::PoolId, currency_id: T::CurrencyId, amount: Balance) -> DispatchResult { - Pallet::::distribute_reward( - pool_id, - currency_id, - amount.to_fixed().ok_or(Error::::TryIntoIntError)?, - ) - } - - fn compute_reward( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - currency_id: T::CurrencyId, - ) -> Result { - Pallet::::compute_reward(pool_id, stake_id, currency_id)? - .try_into() - .map_err(|_| Error::::TryIntoIntError.into()) - } - - fn withdraw_reward( - pool_id: &T::PoolId, - stake_id: &T::StakeId, - currency_id: T::CurrencyId, - ) -> Result { - Pallet::::withdraw_reward(pool_id, stake_id, currency_id)? - .try_into() - .map_err(|_| Error::::TryIntoIntError.into()) - } - - fn get_total_stake(pool_id: &T::PoolId) -> Result { - Pallet::::total_stake(pool_id) - .truncate_to_inner() - .ok_or(Error::::TryIntoIntError)? - .try_into() - .map_err(|_| Error::::TryIntoIntError.into()) - } - - fn get_stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> Result { - Pallet::::stake(pool_id, stake_id) - .truncate_to_inner() - .ok_or(Error::::TryIntoIntError)? - .try_into() - .map_err(|_| Error::::TryIntoIntError.into()) - } - - fn deposit_stake(pool_id: &T::PoolId, stake_id: &T::StakeId, amount: Balance) -> DispatchResult { - Pallet::::deposit_stake( - pool_id, - stake_id, - amount.to_fixed().ok_or(Error::::TryIntoIntError)?, - ) - } - - fn withdraw_stake(pool_id: &T::PoolId, stake_id: &T::StakeId, amount: Balance) -> DispatchResult { - Pallet::::withdraw_stake( - pool_id, - stake_id, - amount.to_fixed().ok_or(Error::::TryIntoIntError)?, - ) - } + type CurrencyId = T::CurrencyId; + + fn distribute_reward( + pool_id: &T::PoolId, + currency_id: T::CurrencyId, + amount: Balance, + ) -> DispatchResult { + Pallet::::distribute_reward( + pool_id, + currency_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } + + fn compute_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result { + Pallet::::compute_reward(pool_id, stake_id, currency_id)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn withdraw_reward( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + currency_id: T::CurrencyId, + ) -> Result { + Pallet::::withdraw_reward(pool_id, stake_id, currency_id)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn get_total_stake(pool_id: &T::PoolId) -> Result { + Pallet::::total_stake(pool_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn get_stake(pool_id: &T::PoolId, stake_id: &T::StakeId) -> Result { + Pallet::::stake(pool_id, stake_id) + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError.into()) + } + + fn deposit_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: Balance, + ) -> DispatchResult { + Pallet::::deposit_stake( + pool_id, + stake_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } + + fn withdraw_stake( + pool_id: &T::PoolId, + stake_id: &T::StakeId, + amount: Balance, + ) -> DispatchResult { + Pallet::::withdraw_stake( + pool_id, + stake_id, + amount.to_fixed().ok_or(Error::::TryIntoIntError)?, + ) + } } impl RewardsApi for () where - Balance: Saturating + PartialOrd + Default, + Balance: Saturating + PartialOrd + Default, { - type CurrencyId = (); - - fn distribute_reward(_: &PoolId, _: Self::CurrencyId, _: Balance) -> DispatchResult { - Ok(()) - } - - fn compute_reward(_: &PoolId, _: &StakeId, _: Self::CurrencyId) -> Result { - Ok(Default::default()) - } - - fn withdraw_reward(_: &PoolId, _: &StakeId, _: Self::CurrencyId) -> Result { - Ok(Default::default()) - } - - fn get_total_stake(_: &PoolId) -> Result { - Ok(Default::default()) - } - - fn get_stake(_: &PoolId, _: &StakeId) -> Result { - Ok(Default::default()) - } - - fn deposit_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { - Ok(()) - } - - fn withdraw_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { - Ok(()) - } -} \ No newline at end of file + type CurrencyId = (); + + fn distribute_reward(_: &PoolId, _: Self::CurrencyId, _: Balance) -> DispatchResult { + Ok(()) + } + + fn compute_reward( + _: &PoolId, + _: &StakeId, + _: Self::CurrencyId, + ) -> Result { + Ok(Default::default()) + } + + fn withdraw_reward( + _: &PoolId, + _: &StakeId, + _: Self::CurrencyId, + ) -> Result { + Ok(Default::default()) + } + + fn get_total_stake(_: &PoolId) -> Result { + Ok(Default::default()) + } + + fn get_stake(_: &PoolId, _: &StakeId) -> Result { + Ok(Default::default()) + } + + fn deposit_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { + Ok(()) + } + + fn withdraw_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { + Ok(()) + } +} diff --git a/code/parachain/frame/reward/src/mock.rs b/code/parachain/frame/reward/src/mock.rs index 5a8f33d93a4..de8492524d2 100644 --- a/code/parachain/frame/reward/src/mock.rs +++ b/code/parachain/frame/reward/src/mock.rs @@ -4,8 +4,8 @@ use frame_support::{parameter_types, traits::Everything}; use sp_arithmetic::FixedI128; use sp_core::H256; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -13,14 +13,14 @@ type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Reward: reward::{Pallet, Call, Storage, Event}, - } + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Reward: reward::{Pallet, Call, Storage, Event}, + } ); pub type CurrencyId = u128; @@ -30,50 +30,50 @@ pub type Index = u64; pub type SignedFixedPoint = FixedI128; parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; } impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Index = Index; - type BlockNumber = BlockNumber; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = Index; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = 0; - pub const GetWrappedCurrencyId: CurrencyId = 3; + pub const GetNativeCurrencyId: CurrencyId = 0; + pub const GetWrappedCurrencyId: CurrencyId = 3; } impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type SignedFixedPoint = SignedFixedPoint; - type PoolId = (); - type StakeId = AccountId; - type CurrencyId = CurrencyId; - // type GetNativeCurrencyId = GetNativeCurrencyId; - // type GetWrappedCurrencyId = GetWrappedCurrencyId; + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = (); + type StakeId = AccountId; + type CurrencyId = CurrencyId; + // type GetNativeCurrencyId = GetNativeCurrencyId; + // type GetWrappedCurrencyId = GetWrappedCurrencyId; } pub type TestError = Error; @@ -84,19 +84,19 @@ pub const BOB: AccountId = 2; pub struct ExtBuilder; impl ExtBuilder { - pub fn build() -> sp_io::TestExternalities { - let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pub fn build() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - storage.into() - } + storage.into() + } } pub fn run_test(test: T) where - T: FnOnce(), + T: FnOnce(), { - ExtBuilder::build().execute_with(|| { - System::set_block_number(1); - test(); - }); + ExtBuilder::build().execute_with(|| { + System::set_block_number(1); + test(); + }); } diff --git a/code/parachain/frame/reward/src/tests.rs b/code/parachain/frame/reward/src/tests.rs index 39bfa18f402..68ab3e98b14 100644 --- a/code/parachain/frame/reward/src/tests.rs +++ b/code/parachain/frame/reward/src/tests.rs @@ -7,15 +7,15 @@ use rand::Rng; use crate::mock::CurrencyId; -const PICA : CurrencyId = 1; -const LP : CurrencyId = 10000; -const KSM : CurrencyId = 4; -const USDT : CurrencyId = 140; +const PICA: CurrencyId = 1; +const LP: CurrencyId = 10000; +const KSM: CurrencyId = 4; +const USDT: CurrencyId = 140; macro_rules! fixed { - ($amount:expr) => { - sp_arithmetic::FixedI128::from($amount) - }; + ($amount:expr) => { + sp_arithmetic::FixedI128::from($amount) + }; } #[test] @@ -40,144 +40,134 @@ fn reproduce_live_state() { #[test] fn should_distribute_rewards_equally() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(100))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 50); - assert_ok!(Reward::compute_reward(&(), &BOB, LP), 50); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(100))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 50); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 50); + }) } #[test] fn should_distribute_uneven_rewards_equally() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(451))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 225); - assert_ok!(Reward::compute_reward(&(), &BOB, LP), 225); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(451))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 225); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 225); + }) } #[test] fn should_not_update_previous_rewards() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(40))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); - - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(20))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); - assert_ok!(Reward::compute_reward(&(), &BOB, LP), 0); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(40))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); + + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(20))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 1000); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 0); + }) } #[test] fn should_withdraw_reward() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(45))); - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(55))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2344))); - assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); - assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), 1054); - assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(45))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(55))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2344))); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); + assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), 1054); + assert_ok!(Reward::compute_reward(&(), &BOB, LP), 1289); + }) } #[test] fn should_withdraw_stake() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(1312))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(4242))); - // rounding in `CheckedDiv` loses some precision - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); - assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(1312))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(1312))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(4242))); + // rounding in `CheckedDiv` loses some precision + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); + assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(1312))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 4241); + }) } #[test] fn should_not_withdraw_stake_if_balance_insufficient() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2000))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 2000); - assert_err!( - Reward::withdraw_stake(&(), &ALICE, fixed!(200)), - TestError::InsufficientFunds - ); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(2000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 2000); + assert_err!(Reward::withdraw_stake(&(), &ALICE, fixed!(200)), TestError::InsufficientFunds); + }) } #[test] fn should_deposit_stake() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); - assert_eq!(Reward::stake(&(), &ALICE), fixed!(50)); - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); - assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 500); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(25))); + assert_eq!(Reward::stake(&(), &ALICE), fixed!(50)); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::compute_reward(&(), &ALICE, LP), 500); + }) } #[test] fn should_not_distribute_rewards_without_stake() { - run_test(|| { - assert_err!( - Reward::distribute_reward(&(), LP, fixed!(1000)), - TestError::ZeroTotalStake - ); - assert_eq!(Reward::total_rewards(LP), fixed!(0)); - }) + run_test(|| { + assert_err!(Reward::distribute_reward(&(), LP, fixed!(1000)), TestError::ZeroTotalStake); + assert_eq!(Reward::total_rewards(LP), fixed!(0)); + }) } #[test] fn should_distribute_with_many_rewards() { - // test that reward tally doesn't overflow - run_test(|| { - let mut rng = rand::thread_rng(); - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(9230404))); - assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(234234444))); - for _ in 0..30 { - // NOTE: this will overflow compute_reward with > u32 - assert_ok!(Reward::distribute_reward( - &(), - LP, - fixed!(rng.gen::() as i128) - )); - } - let alice_reward = Reward::compute_reward(&(), &ALICE, LP).unwrap(); - assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), alice_reward); - let bob_reward = Reward::compute_reward(&(), &BOB, LP).unwrap(); - assert_ok!(Reward::withdraw_reward(&(), &BOB, LP), bob_reward); - }) + // test that reward tally doesn't overflow + run_test(|| { + let mut rng = rand::thread_rng(); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(9230404))); + assert_ok!(Reward::deposit_stake(&(), &BOB, fixed!(234234444))); + for _ in 0..30 { + // NOTE: this will overflow compute_reward with > u32 + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(rng.gen::() as i128))); + } + let alice_reward = Reward::compute_reward(&(), &ALICE, LP).unwrap(); + assert_ok!(Reward::withdraw_reward(&(), &ALICE, LP), alice_reward); + let bob_reward = Reward::compute_reward(&(), &BOB, LP).unwrap(); + assert_ok!(Reward::withdraw_reward(&(), &BOB, LP), bob_reward); + }) } macro_rules! assert_approx_eq { - ($left:expr, $right:expr, $delta:expr) => { - assert!(if $left > $right { $left - $right } else { $right - $left } <= $delta) - }; + ($left:expr, $right:expr, $delta:expr) => { + assert!(if $left > $right { $left - $right } else { $right - $left } <= $delta) + }; } #[test] fn should_distribute_with_different_rewards() { - run_test(|| { - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); - assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); - assert_ok!(Reward::distribute_reward(&(), PICA, fixed!(1000))); - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); - assert_ok!(Reward::distribute_reward(&(), KSM, fixed!(1000))); - assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); - assert_ok!(Reward::distribute_reward(&(), USDT, fixed!(1000))); - - assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(300))); - - assert_approx_eq!(Reward::compute_reward(&(), &ALICE, LP).unwrap(), 1000, 1); - assert_approx_eq!(Reward::compute_reward(&(), &ALICE, PICA).unwrap(), 1000, 1); - assert_approx_eq!(Reward::compute_reward(&(), &ALICE, KSM).unwrap(), 1000, 1); - assert_approx_eq!(Reward::compute_reward(&(), &ALICE, USDT).unwrap(), 1000, 1); - }) + run_test(|| { + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), LP, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), PICA, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), KSM, fixed!(1000))); + assert_ok!(Reward::deposit_stake(&(), &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward(&(), USDT, fixed!(1000))); + + assert_ok!(Reward::withdraw_stake(&(), &ALICE, fixed!(300))); + + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, LP).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, PICA).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, KSM).unwrap(), 1000, 1); + assert_approx_eq!(Reward::compute_reward(&(), &ALICE, USDT).unwrap(), 1000, 1); + }) } diff --git a/code/parachain/node/src/rpc.rs b/code/parachain/node/src/rpc.rs index 7f2eb145828..3e1b7ed2668 100644 --- a/code/parachain/node/src/rpc.rs +++ b/code/parachain/node/src/rpc.rs @@ -21,8 +21,8 @@ use crate::{ client::{FullBackend, FullClient}, runtime::{ assets::ExtendWithAssetsApi, cosmwasm::ExtendWithCosmwasmApi, - crowdloan_rewards::ExtendWithCrowdloanRewardsApi, ibc::ExtendWithIbcApi, - lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, farming::ExtendWithFarmingApi, + crowdloan_rewards::ExtendWithCrowdloanRewardsApi, farming::ExtendWithFarmingApi, + ibc::ExtendWithIbcApi, lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, staking_rewards::ExtendWithStakingRewardsApi, BaseHostRuntimeApis, }, }; diff --git a/code/parachain/node/src/runtime.rs b/code/parachain/node/src/runtime.rs index 02d7491a043..9929fefaf2d 100644 --- a/code/parachain/node/src/runtime.rs +++ b/code/parachain/node/src/runtime.rs @@ -4,8 +4,8 @@ use crowdloan_rewards_rpc::{CrowdloanRewards, CrowdloanRewardsApiServer}; use cumulus_primitives_core::CollectCollationInfo; use ibc_rpc::{IbcApiServer, IbcRpcHandler}; use pablo_rpc::{Pablo, PabloApiServer}; -use reward_rpc::{Reward, RewardApiServer}; use pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi; +use reward_rpc::{Reward, RewardApiServer}; use sp_api::{ApiExt, Metadata, StateBackend}; use sp_block_builder::BlockBuilder; use sp_consensus_aura::{sr25519, AuraApi}; diff --git a/code/parachain/node/src/service.rs b/code/parachain/node/src/service.rs index 65b404015ac..45f1a9b66b8 100644 --- a/code/parachain/node/src/service.rs +++ b/code/parachain/node/src/service.rs @@ -5,8 +5,8 @@ use crate::{ rpc, runtime::{ assets::ExtendWithAssetsApi, cosmwasm::ExtendWithCosmwasmApi, - crowdloan_rewards::ExtendWithCrowdloanRewardsApi, ibc::ExtendWithIbcApi, - lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, farming::ExtendWithFarmingApi, + crowdloan_rewards::ExtendWithCrowdloanRewardsApi, farming::ExtendWithFarmingApi, + ibc::ExtendWithIbcApi, lending::ExtendWithLendingApi, pablo::ExtendWithPabloApi, staking_rewards::ExtendWithStakingRewardsApi, BaseHostRuntimeApis, }, }; diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index bdc8aef067e..61a08425695 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -35,9 +35,9 @@ mod weights; pub mod xcmp; pub use common::xcmp::{MaxInstructions, UnitWeightCost}; pub use fees::{AssetsPaymentHeader, FinalPriceConverter}; +use frame_support::dispatch::DispatchError; use version::{Version, VERSION}; pub use xcmp::XcmConfig; -use frame_support::dispatch::DispatchError; pub use crate::fees::WellKnownForeignToNativePriceConverter; @@ -286,28 +286,28 @@ impl assets::Config for Runtime { type FarmingRewardsInstance = reward::Instance1; impl reward::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SignedFixedPoint = FixedI128; - type PoolId = CurrencyId; - type StakeId = AccountId; - type CurrencyId = CurrencyId; + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = FixedI128; + type PoolId = CurrencyId; + type StakeId = AccountId; + type CurrencyId = CurrencyId; } parameter_types! { - pub const RewardPeriod: BlockNumber = 60_000 / (12000 as BlockNumber); + pub const RewardPeriod: BlockNumber = 60_000 / (12000 as BlockNumber); pub const FarmingPalletId: PalletId = PalletId(*b"mod/farm"); pub FarmingAccount: AccountId = FarmingPalletId::get().into_account_truncating(); } impl farming::Config for Runtime { - type RuntimeEvent = RuntimeEvent; + type RuntimeEvent = RuntimeEvent; type AssetId = CurrencyId; - type FarmingPalletId = FarmingPalletId; - type TreasuryAccountId = FarmingAccount; - type RewardPeriod = RewardPeriod; - type RewardPools = FarmingRewards; - type MultiCurrency = AssetsTransactorRouter; - type WeightInfo = (); + type FarmingPalletId = FarmingPalletId; + type TreasuryAccountId = FarmingAccount; + type RewardPeriod = RewardPeriod; + type RewardPools = FarmingRewards; + type MultiCurrency = AssetsTransactorRouter; + type WeightInfo = (); } parameter_types! { @@ -1146,30 +1146,30 @@ impl_runtime_apis! { } impl reward_rpc_runtime_api::RewardApi< - Block, - AccountId, - CurrencyId, - Balance, - BlockNumber, - sp_runtime::FixedU128 - > for Runtime { - fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError> { - let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; - let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; - Ok(balance) - } - fn estimate_farming_reward( - account_id: AccountId, - pool_currency_id: CurrencyId, - reward_currency_id: CurrencyId, - ) -> Result, DispatchError> { - >::withdraw_reward(&pool_currency_id, &account_id, reward_currency_id)?; - >::distribute_reward(&pool_currency_id, reward_currency_id, Farming::total_rewards(&pool_currency_id, &reward_currency_id))?; - let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; - let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; - Ok(balance) - } - } + Block, + AccountId, + CurrencyId, + Balance, + BlockNumber, + sp_runtime::FixedU128 + > for Runtime { + fn compute_farming_reward(account_id: AccountId, pool_currency_id: CurrencyId, reward_currency_id: CurrencyId) -> Result, DispatchError> { + let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; + let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; + Ok(balance) + } + fn estimate_farming_reward( + account_id: AccountId, + pool_currency_id: CurrencyId, + reward_currency_id: CurrencyId, + ) -> Result, DispatchError> { + >::withdraw_reward(&pool_currency_id, &account_id, reward_currency_id)?; + >::distribute_reward(&pool_currency_id, reward_currency_id, Farming::total_rewards(&pool_currency_id, &reward_currency_id))?; + let amount = >::compute_reward(&pool_currency_id, &account_id, reward_currency_id)?; + let balance = reward_rpc_runtime_api::BalanceWrapper:: { amount }; + Ok(balance) + } + } #[cfg(feature = "runtime-benchmarks")] From 097164b6b09326ad55a15c8d63c1e7259018d0fc Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Wed, 3 May 2023 22:22:04 +0100 Subject: [PATCH 22/25] fix benchmarking tests for farming pallet. --- .../frame/farming/src/benchmarking.rs | 55 ++++++++++--------- code/parachain/frame/farming/src/mock.rs | 1 - 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/code/parachain/frame/farming/src/benchmarking.rs b/code/parachain/frame/farming/src/benchmarking.rs index 2968992e1e3..768ce8104ae 100644 --- a/code/parachain/frame/farming/src/benchmarking.rs +++ b/code/parachain/frame/farming/src/benchmarking.rs @@ -1,11 +1,19 @@ use super::*; -use crate::CurrencyId::Token; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::{assert_ok, traits::Hooks}; use frame_system::RawOrigin; -use primitives::*; use sp_std::vec; +type CurrencyId = u128; + +const PICA: CurrencyId = 1; +const KSM: CurrencyId = 2; +const DOT: CurrencyId = 0; +const REWARD: CurrencyId = 1000; +const CURRENCY_1: CurrencyId = 1001; +const CURRENCY_2: CurrencyId = 1002; +const CURRENCY_3: CurrencyId = 1003; + // Pallets use crate::Pallet as Farming; use frame_system::Pallet as System; @@ -15,7 +23,7 @@ fn default_reward_schedule(reward_currency_id: CurrencyId) -> RewardS let total_amount = reward_schedule.total().unwrap(); assert_ok!(T::MultiCurrency::deposit( - reward_currency_id, + reward_currency_id.into(), &T::TreasuryAccountId::get(), total_amount, )); @@ -28,16 +36,16 @@ fn create_reward_schedule(pool_currency_id: CurrencyId, reward_curren assert_ok!(Farming::::update_reward_schedule( RawOrigin::Root.into(), - pool_currency_id, - reward_currency_id, + pool_currency_id.into(), + reward_currency_id.into(), reward_schedule.period_count, reward_schedule.total().unwrap(), )); } fn create_default_reward_schedule() -> (CurrencyId, CurrencyId) { - let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); - let reward_currency_id = CurrencyId::Token(INTR); + let pool_currency_id = REWARD; + let reward_currency_id = PICA; create_reward_schedule::(pool_currency_id, reward_currency_id); (pool_currency_id, reward_currency_id) } @@ -47,20 +55,15 @@ fn deposit_lp_tokens( account_id: &T::AccountId, amount: BalanceOf, ) { - assert_ok!(T::MultiCurrency::deposit(pool_currency_id, account_id, amount)); + assert_ok!(T::MultiCurrency::deposit(pool_currency_id.into(), account_id, amount)); assert_ok!(Farming::::deposit( RawOrigin::Signed(account_id.clone()).into(), - pool_currency_id, + pool_currency_id.into(), )); } pub fn get_benchmarking_currency_ids() -> Vec<(CurrencyId, CurrencyId)> { - vec![ - (Token(DOT), Token(INTR)), - (Token(KSM), Token(KINT)), - (Token(DOT), Token(IBTC)), - (Token(KSM), Token(KBTC)), - ] + vec![(DOT, PICA), (KSM, CURRENCY_1), (DOT, CURRENCY_2), (KSM, CURRENCY_3)] } benchmarks! { @@ -81,44 +84,44 @@ benchmarks! { } update_reward_schedule { - let pool_currency_id = CurrencyId::LpToken(LpToken::Token(DOT), LpToken::Token(IBTC)); - let reward_currency_id = CurrencyId::Token(INTR); + let pool_currency_id = REWARD; + let reward_currency_id = PICA; let reward_schedule = default_reward_schedule::(reward_currency_id); - }: _(RawOrigin::Root, pool_currency_id, reward_currency_id, reward_schedule.period_count, reward_schedule.total().unwrap()) + }: _(RawOrigin::Root, pool_currency_id.into(), reward_currency_id.into(), reward_schedule.period_count, reward_schedule.total().unwrap()) remove_reward_schedule { let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); - }: _(RawOrigin::Root, pool_currency_id, reward_currency_id) + }: _(RawOrigin::Root, pool_currency_id.into(), reward_currency_id.into()) deposit { let origin: T::AccountId = account("Origin", 0, 0); let (pool_currency_id, _) = create_default_reward_schedule::(); assert_ok!(T::MultiCurrency::deposit( - pool_currency_id, + pool_currency_id.into(), &origin, 100u32.into(), )); - }: _(RawOrigin::Signed(origin), pool_currency_id) + }: _(RawOrigin::Signed(origin), pool_currency_id.into()) withdraw { let origin: T::AccountId = account("Origin", 0, 0); let (pool_currency_id, _) = create_default_reward_schedule::(); let amount = 100u32.into(); - deposit_lp_tokens::(pool_currency_id, &origin, amount); + deposit_lp_tokens::(pool_currency_id.into(), &origin, amount); - }: _(RawOrigin::Signed(origin), pool_currency_id, amount) + }: _(RawOrigin::Signed(origin), pool_currency_id.into(), amount) claim { let origin: T::AccountId = account("Origin", 0, 0); let (pool_currency_id, reward_currency_id) = create_default_reward_schedule::(); let amount = 100u32.into(); - deposit_lp_tokens::(pool_currency_id, &origin, amount); - assert_ok!(T::RewardPools::distribute_reward(&pool_currency_id, reward_currency_id, amount)); + deposit_lp_tokens::(pool_currency_id.into(), &origin, amount); + assert_ok!(T::RewardPools::distribute_reward(&pool_currency_id.into(), reward_currency_id.into(), amount)); - }: _(RawOrigin::Signed(origin), pool_currency_id, reward_currency_id) + }: _(RawOrigin::Signed(origin), pool_currency_id.into(), reward_currency_id.into()) } impl_benchmark_test_suite!(Farming, crate::mock::ExtBuilder::build(), crate::mock::Test); diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs index 87f8c10c099..9549c52797a 100644 --- a/code/parachain/frame/farming/src/mock.rs +++ b/code/parachain/frame/farming/src/mock.rs @@ -5,7 +5,6 @@ use frame_support::{ PalletId, }; use orml_traits::parameter_type_with_key; -// pub use primitives::currency::CurrencyId; use sp_arithmetic::FixedI128; use sp_core::H256; use sp_runtime::{ From 2fa56c75d1961ed1b5cd596bc7e685df95f33613 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 4 May 2023 10:15:57 +0100 Subject: [PATCH 23/25] fix cargo clippy --- .../frame/farming/src/default_weights.rs | 76 +++++++++---------- code/parachain/frame/farming/src/lib.rs | 10 +-- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/code/parachain/frame/farming/src/default_weights.rs b/code/parachain/frame/farming/src/default_weights.rs index a9db3c76d25..eb39dd54de2 100644 --- a/code/parachain/frame/farming/src/default_weights.rs +++ b/code/parachain/frame/farming/src/default_weights.rs @@ -48,27 +48,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Farming RewardSchedules (r:1 w:0) // Storage: FarmingRewards TotalStake (r:1 w:0) fn on_initialize(c: u32, ) -> Weight { - Weight::from_ref_time(18_073_005 as u64) + Weight::from_ref_time(18_073_005u64) // Standard Error: 183_362 - .saturating_add(Weight::from_ref_time(18_555_611 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(Weight::from_ref_time(18_555_611u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1u64)) + .saturating_add(T::DbWeight::get().reads((2u64).saturating_mul(c as u64))) } // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:2 w:1) // Storage: Farming RewardSchedules (r:1 w:1) fn update_reward_schedule() -> Weight { - Weight::from_ref_time(105_531_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + Weight::from_ref_time(105_531_000u64) + .saturating_add(T::DbWeight::get().reads(5u64)) + .saturating_add(T::DbWeight::get().writes(4u64)) } // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:1 w:0) // Storage: Farming RewardSchedules (r:0 w:1) fn remove_reward_schedule() -> Weight { - Weight::from_ref_time(83_988_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + Weight::from_ref_time(83_988_000u64) + .saturating_add(T::DbWeight::get().reads(3u64)) + .saturating_add(T::DbWeight::get().writes(3u64)) } // Storage: Farming RewardSchedules (r:2 w:0) // Storage: Tokens Accounts (r:1 w:1) @@ -77,9 +77,9 @@ impl WeightInfo for SubstrateWeight { // Storage: FarmingRewards RewardTally (r:2 w:2) // Storage: FarmingRewards RewardPerToken (r:2 w:0) fn deposit() -> Weight { - Weight::from_ref_time(108_507_000 as u64) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + Weight::from_ref_time(108_507_000u64) + .saturating_add(T::DbWeight::get().reads(9u64)) + .saturating_add(T::DbWeight::get().writes(5u64)) } // Storage: Tokens Accounts (r:1 w:1) // Storage: FarmingRewards Stake (r:1 w:1) @@ -87,9 +87,9 @@ impl WeightInfo for SubstrateWeight { // Storage: FarmingRewards RewardTally (r:2 w:2) // Storage: FarmingRewards RewardPerToken (r:2 w:0) fn withdraw() -> Weight { - Weight::from_ref_time(96_703_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + Weight::from_ref_time(96_703_000u64) + .saturating_add(T::DbWeight::get().reads(7u64)) + .saturating_add(T::DbWeight::get().writes(5u64)) } // Storage: FarmingRewards Stake (r:1 w:0) // Storage: FarmingRewards RewardPerToken (r:1 w:0) @@ -98,9 +98,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:2 w:1) fn claim() -> Weight { - Weight::from_ref_time(136_142_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + Weight::from_ref_time(136_142_000u64) + .saturating_add(T::DbWeight::get().reads(8u64)) + .saturating_add(T::DbWeight::get().writes(5u64)) } } @@ -109,27 +109,27 @@ impl WeightInfo for () { // Storage: Farming RewardSchedules (r:1 w:0) // Storage: FarmingRewards TotalStake (r:1 w:0) fn on_initialize(c: u32, ) -> Weight { - Weight::from_ref_time(18_073_005 as u64) + Weight::from_ref_time(18_073_005u64) // Standard Error: 183_362 - .saturating_add(Weight::from_ref_time(18_555_611 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(Weight::from_ref_time(18_555_611u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1u64)) + .saturating_add(RocksDbWeight::get().reads((2u64).saturating_mul(c as u64))) } // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:2 w:1) // Storage: Farming RewardSchedules (r:1 w:1) fn update_reward_schedule() -> Weight { - Weight::from_ref_time(105_531_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + Weight::from_ref_time(105_531_000u64) + .saturating_add(RocksDbWeight::get().reads(5u64)) + .saturating_add(RocksDbWeight::get().writes(4u64)) } // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:1 w:0) // Storage: Farming RewardSchedules (r:0 w:1) fn remove_reward_schedule() -> Weight { - Weight::from_ref_time(83_988_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + Weight::from_ref_time(83_988_000u64) + .saturating_add(RocksDbWeight::get().reads(3u64)) + .saturating_add(RocksDbWeight::get().writes(3u64)) } // Storage: Farming RewardSchedules (r:2 w:0) // Storage: Tokens Accounts (r:1 w:1) @@ -138,9 +138,9 @@ impl WeightInfo for () { // Storage: FarmingRewards RewardTally (r:2 w:2) // Storage: FarmingRewards RewardPerToken (r:2 w:0) fn deposit() -> Weight { - Weight::from_ref_time(108_507_000 as u64) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + Weight::from_ref_time(108_507_000u64) + .saturating_add(RocksDbWeight::get().reads(9u64)) + .saturating_add(RocksDbWeight::get().writes(5u64)) } // Storage: Tokens Accounts (r:1 w:1) // Storage: FarmingRewards Stake (r:1 w:1) @@ -148,9 +148,9 @@ impl WeightInfo for () { // Storage: FarmingRewards RewardTally (r:2 w:2) // Storage: FarmingRewards RewardPerToken (r:2 w:0) fn withdraw() -> Weight { - Weight::from_ref_time(96_703_000 as u64) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + Weight::from_ref_time(96_703_000u64) + .saturating_add(RocksDbWeight::get().reads(7u64)) + .saturating_add(RocksDbWeight::get().writes(5u64)) } // Storage: FarmingRewards Stake (r:1 w:0) // Storage: FarmingRewards RewardPerToken (r:1 w:0) @@ -159,8 +159,8 @@ impl WeightInfo for () { // Storage: Tokens Accounts (r:2 w:2) // Storage: System Account (r:2 w:1) fn claim() -> Weight { - Weight::from_ref_time(136_142_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + Weight::from_ref_time(136_142_000u64) + .saturating_add(RocksDbWeight::get().reads(8u64)) + .saturating_add(RocksDbWeight::get().writes(5u64)) } } diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 9fc79e05004..0fe57c1eabc 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -181,11 +181,9 @@ pub mod pallet { schedules.into_iter() { if let Some(amount) = reward_schedule.take() { - if let Ok(_) = Self::try_distribute_reward( - pool_currency_id, - reward_currency_id, - amount, - ) { + if Self::try_distribute_reward(pool_currency_id, reward_currency_id, amount) + .is_ok() + { // only update the schedule if we could distribute the reward RewardSchedules::::insert( pool_currency_id, @@ -285,7 +283,7 @@ pub mod pallet { period_count: total_period_count, per_period: total_per_period, }); - Ok(().into()) + Ok(()) }, ) } From 869d5e708e9cf6219fecffce2dea978d26086d98 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 4 May 2023 13:13:20 +0100 Subject: [PATCH 24/25] remove commented code and --- code/parachain/frame/farming/src/lib.rs | 3 +-- code/parachain/frame/farming/src/mock.rs | 5 ----- code/parachain/frame/reward/src/lib.rs | 10 ++-------- code/parachain/frame/reward/src/mock.rs | 7 ------- 4 files changed, 3 insertions(+), 22 deletions(-) diff --git a/code/parachain/frame/farming/src/lib.rs b/code/parachain/frame/farming/src/lib.rs index 0fe57c1eabc..b12d1fac854 100644 --- a/code/parachain/frame/farming/src/lib.rs +++ b/code/parachain/frame/farming/src/lib.rs @@ -13,7 +13,7 @@ //! //! The following design decisions have been made: //! - The reward schedule is configured as a matrix such that a staked token (e.g., an AMM LP token) -//! and an incentive token (e.g., INTR or DOT) represent one reward schedule. This enables adding +//! and an incentive token (e.g., PICA or DOT) represent one reward schedule. This enables adding //! multiple reward currencies per staked token. //! - Rewards can be increased but not decreased unless the schedule is explicitly removed. //! - The rewards period cannot change without a migration. @@ -200,7 +200,6 @@ pub mod pallet { } else { // period count is zero RewardSchedules::::remove(pool_currency_id, reward_currency_id); - // TODO: sweep leftover rewards? } } T::WeightInfo::on_initialize(count) diff --git a/code/parachain/frame/farming/src/mock.rs b/code/parachain/frame/farming/src/mock.rs index 9549c52797a..98ebfc59522 100644 --- a/code/parachain/frame/farming/src/mock.rs +++ b/code/parachain/frame/farming/src/mock.rs @@ -72,9 +72,6 @@ impl frame_system::Config for Test { } parameter_types! { - // pub const GetNativeCurrencyId: CurrencyId = 1; - // pub const GetRelayChainCurrencyId: CurrencyId = 2; - // pub const GetWrappedCurrencyId: CurrencyId = 3; pub const MaxLocks: u32 = 50; } @@ -104,8 +101,6 @@ impl reward::Config for Test { type PoolId = CurrencyId; type StakeId = AccountId; type CurrencyId = CurrencyId; - // type GetNativeCurrencyId = GetNativeCurrencyId; - // type GetWrappedCurrencyId = GetWrappedCurrencyId; } parameter_types! { diff --git a/code/parachain/frame/reward/src/lib.rs b/code/parachain/frame/reward/src/lib.rs index f34b1e0b74e..10a94fce505 100644 --- a/code/parachain/frame/reward/src/lib.rs +++ b/code/parachain/frame/reward/src/lib.rs @@ -89,12 +89,6 @@ pub mod pallet { /// The currency ID type. type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; - - // #[pallet::constant] - // type GetNativeCurrencyId: Get; - - // #[pallet::constant] - // type GetWrappedCurrencyId: Get; } // The pallet's events @@ -322,11 +316,11 @@ impl, I: 'static> Pallet { ) -> Result< as FixedPointNumber>::Inner, DispatchError> { let stake = Self::stake(pool_id, stake_id); let reward_per_token = Self::reward_per_token(currency_id, pool_id); - // FIXME: this can easily overflow with large numbers + let stake_mul_reward_per_token = stake.checked_mul(&reward_per_token).ok_or(ArithmeticError::Overflow)?; let reward_tally = >::get(currency_id, (pool_id, stake_id)); - // TODO: this can probably be saturated + let reward = stake_mul_reward_per_token .checked_sub(&reward_tally) .ok_or(ArithmeticError::Underflow)? diff --git a/code/parachain/frame/reward/src/mock.rs b/code/parachain/frame/reward/src/mock.rs index de8492524d2..447cd0e54c7 100644 --- a/code/parachain/frame/reward/src/mock.rs +++ b/code/parachain/frame/reward/src/mock.rs @@ -61,19 +61,12 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const GetNativeCurrencyId: CurrencyId = 0; - pub const GetWrappedCurrencyId: CurrencyId = 3; -} - impl Config for Test { type RuntimeEvent = RuntimeEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = (); type StakeId = AccountId; type CurrencyId = CurrencyId; - // type GetNativeCurrencyId = GetNativeCurrencyId; - // type GetWrappedCurrencyId = GetWrappedCurrencyId; } pub type TestError = Error; From fea4c3e4993fdfdeaee09c78283a36eea39a4107 Mon Sep 17 00:00:00 2001 From: cr4pt0 Date: Thu, 4 May 2023 13:15:43 +0100 Subject: [PATCH 25/25] update pub const RewardPeriod: BlockNumber = 5 --- code/parachain/runtime/picasso/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/parachain/runtime/picasso/src/lib.rs b/code/parachain/runtime/picasso/src/lib.rs index 61a08425695..444dceeb13b 100644 --- a/code/parachain/runtime/picasso/src/lib.rs +++ b/code/parachain/runtime/picasso/src/lib.rs @@ -294,7 +294,7 @@ impl reward::Config for Runtime { } parameter_types! { - pub const RewardPeriod: BlockNumber = 60_000 / (12000 as BlockNumber); + pub const RewardPeriod: BlockNumber = 5; //1 minute pub const FarmingPalletId: PalletId = PalletId(*b"mod/farm"); pub FarmingAccount: AccountId = FarmingPalletId::get().into_account_truncating(); }