From 582f234922b8614c4a84ed6d24f2636fa4f94843 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 6 Oct 2023 18:15:10 -0300 Subject: [PATCH 01/52] WIP adapting pool rewards and calling from other parts of the runtime with different types --- Cargo.lock | 25 ++ Cargo.toml | 1 + pallets/nomination/Cargo.toml | 1 + pallets/nomination/src/ext.rs | 31 ++ pallets/nomination/src/lib.rs | 15 +- pallets/pooled-rewards/Cargo.toml | 56 +++ pallets/pooled-rewards/src/lib.rs | 571 ++++++++++++++++++++++++++++ pallets/pooled-rewards/src/mock.rs | 130 +++++++ pallets/pooled-rewards/src/tests.rs | 297 +++++++++++++++ pallets/vault-registry/Cargo.toml | 2 + pallets/vault-registry/src/lib.rs | 8 +- 11 files changed, 1134 insertions(+), 3 deletions(-) create mode 100644 pallets/pooled-rewards/Cargo.toml create mode 100644 pallets/pooled-rewards/src/lib.rs create mode 100644 pallets/pooled-rewards/src/mock.rs create mode 100644 pallets/pooled-rewards/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index c56b549ab..667c344e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5130,6 +5130,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", + "pooled-rewards", "reward", "scale-info", "security", @@ -6037,6 +6038,29 @@ dependencies = [ "universal-hash 0.5.1", ] +[[package]] +name = "pooled-rewards" +version = "1.0.0" +dependencies = [ + "currency", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "mocktopus", + "pallet-timestamp", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "spacewalk-primitives", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -10862,6 +10886,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", + "pooled-rewards", "pretty_assertions", "reward", "scale-info", diff --git a/Cargo.toml b/Cargo.toml index a23de9ab2..3715cce6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "pallets/fee", "pallets/nomination", "pallets/oracle", + "pallets/pooled-rewards", "pallets/reward", "pallets/staking", "pallets/stellar-relay", diff --git a/pallets/nomination/Cargo.toml b/pallets/nomination/Cargo.toml index b4c392a76..875effa6a 100644 --- a/pallets/nomination/Cargo.toml +++ b/pallets/nomination/Cargo.toml @@ -30,6 +30,7 @@ fee = { path = "../fee", default-features = false } oracle = { path = "../oracle", default-features = false } reward = { path = "../reward", default-features = false } staking = { path = "../staking", default-features = false } +pooled-rewards = { path = "../pooled-rewards", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index f6f968d02..08afe93ec 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -115,3 +115,34 @@ pub(crate) mod staking { T::VaultStaking::force_refund(vault_id) } } + +#[cfg_attr(test, mockable)] +pub(crate) mod pooled_rewards { + use crate::BalanceOf; + use frame_support::dispatch::DispatchResult; + use pooled_rewards::RewardsApi; + use primitives::BalanceToFixedPoint; + use sp_arithmetic::FixedPointNumber; + + type VaultBalance = BalanceOf; + type RewardsFixedPoint = ::SignedFixedPoint; + type InnerRewardsFixedPoint = + <::SignedFixedPoint as FixedPointNumber>::Inner; + + pub fn deposit_stake( + pool_id: &::CurrencyId, + stake_id: &::AccountId, + amount: BalanceOf, + ) -> DispatchResult + where + BalanceOf: From>, + + BalanceOf: BalanceToFixedPoint>, + { + as RewardsApi< + ::CurrencyId, + ::AccountId, + BalanceOf, + >>::deposit_stake(pool_id.into(), stake_id.into(), amount) + } +} diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index 23098455c..8a20c6577 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -47,9 +47,15 @@ pub mod pallet { /// ## Configuration /// The pallet's configuration trait. + /// + /// TODO is it okay to do tight coupling also for pooled-rewards?? #[pallet::config] pub trait Config: - frame_system::Config + security::Config + vault_registry::Config + fee::Config + frame_system::Config + + security::Config + + vault_registry::Config + + fee::Config + + pooled_rewards::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -287,6 +293,13 @@ impl Pallet { amount.lock_on(&vault_id.account_id)?; ext::vault_registry::try_increase_total_backing_collateral(&vault_id.currencies, &amount)?; + // deposit into pool rewards + ext::pooled_rewards::deposit_stake::( + &vault_id.currencies.collateral, + nominator_id, + amount, + )?; + Self::deposit_event(Event::::DepositCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), diff --git a/pallets/pooled-rewards/Cargo.toml b/pallets/pooled-rewards/Cargo.toml new file mode 100644 index 000000000..e8209026c --- /dev/null +++ b/pallets/pooled-rewards/Cargo.toml @@ -0,0 +1,56 @@ +[package] +authors = ["Pendulum Chain "] +description = "Pooled Reward module" +edition = "2021" +name = "pooled-rewards" +version = "1.0.0" + +[dependencies] +log = { version = "0.4.17", default-features = false } +serde = { version = "1.0.130", default-features = false, features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } + +# Parachain dependencies +primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } + +# Substrate dependencies +sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false, optional = true } + +[dev-dependencies] +mocktopus = "0.8.0" +rand = "0.8.3" +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } + +currency = { path = "../currency", default-features = false, features = ["testing-constants"] } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "currency/std", + "primitives/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] \ No newline at end of file diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs new file mode 100644 index 000000000..2a136a8cb --- /dev/null +++ b/pallets/pooled-rewards/src/lib.rs @@ -0,0 +1,571 @@ +//! # 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)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +use codec::{Decode, Encode, EncodeLike}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::Get, +}; +use primitives::{BalanceToFixedPoint, TruncateFixedPointToInt}; +use scale_info::TypeInfo; +use sp_arithmetic::FixedPointNumber; +use sp_runtime::{ + traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, MaybeSerializeDeserialize, Saturating, Zero, + }, + ArithmeticError, +}; +use sp_std::{cmp::PartialOrd, convert::TryInto, fmt::Debug}; + +pub(crate) type SignedFixedPoint = >::SignedFixedPoint; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, BoundedBTreeSet}; + + /// ## 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 + + MaxEncodedLen; + + /// The pool identifier type. + type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The stake identifier type. + type StakeId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaxEncodedLen + + From; + + /// The currency ID type. + type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; + + /// The maximum number of reward currencies. + #[pallet::constant] + type MaxRewardCurrencies: 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, + /// Maximum rewards currencies reached. + MaxRewardCurrencies, + } + + #[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, + BoundedBTreeSet, + ValueQuery, + >; + + #[pallet::pallet] + 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::::try_mutate(pool_id, |reward_currencies| { + reward_currencies + .try_insert(currency_id) + .map_err(|_| Error::::MaxRewardCurrencies) + })?; + + 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 + Copy, +{ + type CurrencyId; + + fn reward_currencies_len(pool_id: &PoolId) -> u32; + + /// 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) -> Result { + let amount = Self::get_stake(pool_id, stake_id)?; + Self::withdraw_stake(pool_id, stake_id, amount)?; + Ok(amount) + } + + /// 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 + Copy, + ::Inner: TryInto, +{ + type CurrencyId = T::CurrencyId; + + fn reward_currencies_len(pool_id: &T::PoolId) -> u32 { + RewardCurrencies::::get(pool_id).len() as u32 + } + + 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 + Copy, +{ + type CurrencyId = (); + + fn reward_currencies_len(_: &PoolId) -> u32 { + Default::default() + } + + 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/pallets/pooled-rewards/src/mock.rs b/pallets/pooled-rewards/src/mock.rs new file mode 100644 index 000000000..9632b2544 --- /dev/null +++ b/pallets/pooled-rewards/src/mock.rs @@ -0,0 +1,130 @@ +use frame_support::{parameter_types, traits::Everything}; +use sp_arithmetic::FixedI128; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +pub use currency::testing_constants::{ + DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, DEFAULT_WRAPPED_CURRENCY, +}; +pub use primitives::{CurrencyId, VaultCurrencyPair, VaultId}; + +use crate as reward; +use crate::{Config, Error}; + +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 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; + pub const MaxRewardCurrencies: u32= 10; +} + +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 = TestEvent; + 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 = DEFAULT_NATIVE_CURRENCY; +} + +pub const DEFAULT_WRAPPED_CURRENCY2: CurrencyId = CurrencyId::AlphaNum4( + *b"USDT", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); +pub const DEFAULT_WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( + *b"MXN\0", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + +pub const DEFAULT_WRAPPED_CURRENCY4: CurrencyId = CurrencyId::AlphaNum4( + *b"ARST", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + +impl Config for Test { + type RuntimeEvent = TestEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type CurrencyId = CurrencyId; + type StakeId = AccountId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + +pub type TestEvent = RuntimeEvent; +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/pallets/pooled-rewards/src/tests.rs b/pallets/pooled-rewards/src/tests.rs new file mode 100644 index 000000000..aa3d08b2d --- /dev/null +++ b/pallets/pooled-rewards/src/tests.rs @@ -0,0 +1,297 @@ +/// Tests for Reward +use crate::mock::*; +use frame_support::{assert_err, assert_ok}; +use rand::Rng; + +// type Event = crate::Event; + +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 = DEFAULT_NATIVE_CURRENCY; + + // state for a3eFe9M2HbAgrQrShEDH2CEvXACtzLhSf4JGkwuT9SQ1EV4ti at block 0xb47ed0e773e25c81da2cc606495ab6f716c3c2024f9beb361605860912fee652 + crate::RewardPerToken::::insert(currency, DEFAULT_COLLATERAL_CURRENCY, f(1_699_249_738_518_636_122_154_288_694)); + crate::RewardTally::::insert(currency, (DEFAULT_COLLATERAL_CURRENCY, ALICE), f(164_605_943_476_265_834_062_592_062_507_811_208)); + crate::Stake::::insert((DEFAULT_COLLATERAL_CURRENCY, 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(DEFAULT_COLLATERAL_CURRENCY, f(2_253_803_217_000_000_000_000_000_000)); + + assert_ok!(Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, currency), 1376582365513566); + }) +} + +#[test] +fn should_distribute_rewards_equally() { + run_test(|| { + //Pool Id is the currency id of collateral + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(100) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 50 + ); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 50 + ); + }) +} + +#[test] +fn should_distribute_uneven_rewards_equally() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(50))); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(451) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 225 + ); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 225 + ); + }) +} + +#[test] +fn should_not_update_previous_rewards() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(40))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(1000) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 1000 + ); + + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(20))); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 1000 + ); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 0 + ); + }) +} + +#[test] +fn should_withdraw_reward() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(45))); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(55))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(2344) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 1289 + ); + assert_ok!( + Reward::withdraw_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 1054 + ); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 1289 + ); + }) +} + +#[test] +fn should_withdraw_stake() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(1312))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(4242) + )); + // rounding in `CheckedDiv` loses some precision + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 4241 + ); + assert_ok!(Reward::withdraw_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(1312))); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 4241 + ); + }) +} + +#[test] +fn should_not_withdraw_stake_if_balance_insufficient() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(2000) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 2000 + ); + assert_err!( + Reward::withdraw_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(200)), + TestError::InsufficientFunds + ); + }) +} + +#[test] +fn should_deposit_stake() { + run_test(|| { + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(25))); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(25))); + assert_eq!(Reward::stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE), fixed!(50)); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(50))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(1000) + )); + assert_ok!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + 500 + ); + }) +} + +#[test] +fn should_not_distribute_rewards_without_stake() { + run_test(|| { + assert_err!( + Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(1000) + ), + TestError::ZeroTotalStake + ); + assert_eq!(Reward::total_rewards(DEFAULT_NATIVE_CURRENCY), 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(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(9230404))); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &BOB, fixed!(234234444))); + for _ in 0..30 { + // NOTE: this will overflow compute_reward with > u32 + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(rng.gen::() as i128) + )); + } + let alice_reward = + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY) + .unwrap(); + assert_ok!( + Reward::withdraw_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY), + alice_reward + ); + let bob_reward = + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY) + .unwrap(); + assert_ok!( + Reward::withdraw_reward(&DEFAULT_COLLATERAL_CURRENCY, &BOB, DEFAULT_NATIVE_CURRENCY), + 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(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_NATIVE_CURRENCY, + fixed!(1000) + )); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_WRAPPED_CURRENCY2, + fixed!(1000) + )); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_WRAPPED_CURRENCY3, + fixed!(1000) + )); + assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); + assert_ok!(Reward::distribute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + DEFAULT_WRAPPED_CURRENCY4, + fixed!(1000) + )); + + assert_ok!(Reward::withdraw_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(300))); + + assert_approx_eq!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_NATIVE_CURRENCY) + .unwrap(), + 1000, + 1 + ); + assert_approx_eq!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY2) + .unwrap(), + 1000, + 1 + ); + assert_approx_eq!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY3) + .unwrap(), + 1000, + 1 + ); + assert_approx_eq!( + Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY4) + .unwrap(), + 1000, + 1 + ); + }) +} diff --git a/pallets/vault-registry/Cargo.toml b/pallets/vault-registry/Cargo.toml index 784bfdd78..26e66b5ba 100644 --- a/pallets/vault-registry/Cargo.toml +++ b/pallets/vault-registry/Cargo.toml @@ -38,6 +38,8 @@ reward = {path = "../reward", default-features = false} security = {path = "../security", default-features = false} staking = {path = "../staking", default-features = false} +pooled-rewards = { path = "../pooled-rewards", default-features = false } + # Orml dependencies orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false } orml-tokens = {git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false} diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 7e1199576..d6a378b7f 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -31,7 +31,8 @@ use sp_std::{ pub use currency::Amount; pub use default_weights::{SubstrateWeight, WeightInfo}; pub use pallet::*; -use primitives::{StellarPublicKeyRaw, VaultCurrencyPair}; + +use primitives::{BalanceToFixedPoint, StellarPublicKeyRaw, VaultCurrencyPair}; use crate::types::{ BalanceOf, CurrencyId, DefaultSystemVault, DefaultVaultCurrencyPair, RichSystemVault, @@ -80,6 +81,7 @@ pub mod pallet { + security::Config + currency::Config> + fee::Config, SignedInner = SignedInner> + + pooled_rewards::Config { /// The vault module id, used for deriving its sovereign account ID. #[pallet::constant] // put the constant in metadata @@ -103,7 +105,9 @@ pub mod pallet { + Default + Debug + TypeInfo - + MaxEncodedLen; + + MaxEncodedLen + + From<<::SignedFixedPoint as FixedPointNumber>::Inner> + + BalanceToFixedPoint<::SignedFixedPoint>; /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; From 0ca6ba91b5eb532e01b8bfbc07bb3512df535ca9 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 9 Oct 2023 19:51:37 -0300 Subject: [PATCH 02/52] pallet pooled rewards configured and connected to pallet nomination --- Cargo.lock | 2 + pallets/nomination/src/ext.rs | 33 +++++++--------- pallets/nomination/src/lib.rs | 16 +++++++- pallets/nomination/src/mock.rs | 17 +++++++- pallets/pooled-rewards/Cargo.toml | 4 +- pallets/pooled-rewards/src/lib.rs | 62 ++++++++++++++++++++++++++---- pallets/pooled-rewards/src/mock.rs | 53 +++++++++++++------------ pallets/vault-registry/src/lib.rs | 7 +--- testchain/runtime/Cargo.toml | 2 + testchain/runtime/src/lib.rs | 15 ++++++++ 10 files changed, 151 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 667c344e8..22cda9709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6043,6 +6043,7 @@ name = "pooled-rewards" version = "1.0.0" dependencies = [ "currency", + "fee", "frame-benchmarking", "frame-support", "frame-system", @@ -9538,6 +9539,7 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", + "pooled-rewards", "pretty_assertions", "redeem", "replace", diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index 08afe93ec..35d2244a0 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -118,31 +118,26 @@ pub(crate) mod staking { #[cfg_attr(test, mockable)] pub(crate) mod pooled_rewards { - use crate::BalanceOf; + + use currency::{CurrencyId, Amount}; use frame_support::dispatch::DispatchResult; use pooled_rewards::RewardsApi; - use primitives::BalanceToFixedPoint; - use sp_arithmetic::FixedPointNumber; - - type VaultBalance = BalanceOf; - type RewardsFixedPoint = ::SignedFixedPoint; - type InnerRewardsFixedPoint = - <::SignedFixedPoint as FixedPointNumber>::Inner; pub fn deposit_stake( - pool_id: &::CurrencyId, - stake_id: &::AccountId, - amount: BalanceOf, + currency_id: &CurrencyId, + account_id: &::AccountId, + amount: Amount, ) -> DispatchResult - where - BalanceOf: From>, + { + T::PoolRewards::deposit_stake(currency_id, account_id,amount.amount()) + } - BalanceOf: BalanceToFixedPoint>, + pub fn withdraw_stake( + currency_id: &CurrencyId, + account_id: &::AccountId, + amount: Amount, + ) -> DispatchResult { - as RewardsApi< - ::CurrencyId, - ::AccountId, - BalanceOf, - >>::deposit_stake(pool_id.into(), stake_id.into(), amount) + T::PoolRewards::withdraw_stake(currency_id, account_id,amount.amount()) } } diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index 8a20c6577..d0ef09a34 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -44,6 +44,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use vault_registry::types::DefaultVaultCurrencyPair; + use currency::{CurrencyId}; /// ## Configuration /// The pallet's configuration trait. @@ -62,6 +63,9 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; + + type PoolRewards: pooled_rewards::RewardsApi, Self::AccountId, BalanceOf>; + } #[pallet::event] @@ -251,6 +255,14 @@ impl Pallet { ext::fee::withdraw_all_vault_rewards::(vault_id)?; // withdraw `amount` of stake from the vault staking pool ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; + + //withdraw from the pooled reward + ext::pooled_rewards::withdraw_stake::( + &vault_id.collateral_currency(), + nominator_id, + amount.clone(), + )?; + amount.unlock_on(&vault_id.account_id)?; amount.transfer(&vault_id.account_id, nominator_id)?; @@ -295,9 +307,9 @@ impl Pallet { // deposit into pool rewards ext::pooled_rewards::deposit_stake::( - &vault_id.currencies.collateral, + &vault_id.collateral_currency(), nominator_id, - amount, + amount.clone(), )?; Self::deposit_event(Event::::DepositCollateral { diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index 2b437752f..46f5ed2fb 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -51,7 +51,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: reward::{Pallet, Call, Storage, Event}, - + PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, @@ -60,6 +60,7 @@ frame_support::construct_runtime!( Nomination: nomination::{Pallet, Call, Config, Storage, Event}, Staking: staking::{Pallet, Storage, Event}, Currency: currency::{Pallet}, + } ); @@ -290,9 +291,23 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { + type RuntimeEvent = TestEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type CurrencyId= CurrencyId; + type StakeId = AccountId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + impl Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = crate::SubstrateWeight; + type PoolRewards = PooledRewards; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/pooled-rewards/Cargo.toml b/pallets/pooled-rewards/Cargo.toml index e8209026c..f9c8e4987 100644 --- a/pallets/pooled-rewards/Cargo.toml +++ b/pallets/pooled-rewards/Cargo.toml @@ -13,6 +13,8 @@ scale-info = { version = "2.0.0", default-features = false, features = ["derive" # Parachain dependencies primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } +fee = { path = "../fee", default-features = false } + # Substrate dependencies sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } @@ -30,7 +32,6 @@ mocktopus = "0.8.0" rand = "0.8.3" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } - currency = { path = "../currency", default-features = false, features = ["testing-constants"] } [features] @@ -39,6 +40,7 @@ std = [ "serde", "codec/std", "currency/std", + "fee/std", "primitives/std", "sp-arithmetic/std", "sp-core/std", diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs index 2a136a8cb..2c0f3f4f7 100644 --- a/pallets/pooled-rewards/src/lib.rs +++ b/pallets/pooled-rewards/src/lib.rs @@ -35,12 +35,12 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, BoundedBTreeSet}; - + use frame_support::{pallet_prelude::*, BoundedBTreeSet}; + /// ## Configuration /// The pallet's configuration trait. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -53,17 +53,20 @@ pub mod pallet { + Decode + TypeInfo + MaxEncodedLen; - + /// The pool identifier type. - type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + type PoolId: Parameter + + Member + + MaybeSerializeDeserialize + + Debug + + MaxEncodedLen; /// The stake identifier type. type StakeId: Parameter + Member + MaybeSerializeDeserialize + Debug - + MaxEncodedLen - + From; + + MaxEncodedLen; /// The currency ID type. type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; @@ -437,6 +440,18 @@ where } } +pub trait ModifyStakePool +where + Balance: Saturating + PartialOrd + Copy, +{ + /// 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; + +} + impl RewardsApi for Pallet where T: Config, @@ -523,6 +538,39 @@ where } } +impl ModifyStakePool for Pallet +where + T: Config, + I: 'static, + Balance: BalanceToFixedPoint> + Saturating + PartialOrd + Copy, + +{ + 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 + Copy, diff --git a/pallets/pooled-rewards/src/mock.rs b/pallets/pooled-rewards/src/mock.rs index 9632b2544..603edb71e 100644 --- a/pallets/pooled-rewards/src/mock.rs +++ b/pallets/pooled-rewards/src/mock.rs @@ -11,9 +11,10 @@ pub use currency::testing_constants::{ }; pub use primitives::{CurrencyId, VaultCurrencyPair, VaultId}; -use crate as reward; +use crate as pooled_rewards; use crate::{Config, Error}; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -25,7 +26,7 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Storage, Config, Event}, - Reward: reward::{Pallet, Call, Storage, Event}, + Reward: pooled_rewards::{Pallet, Call, Storage, Event}, } ); @@ -40,6 +41,30 @@ parameter_types! { pub const MaxRewardCurrencies: u32= 10; } +pub const DEFAULT_WRAPPED_CURRENCY2: CurrencyId = CurrencyId::AlphaNum4( + *b"USDT", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); +pub const DEFAULT_WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( + *b"MXN\0", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + +pub const DEFAULT_WRAPPED_CURRENCY4: CurrencyId = CurrencyId::AlphaNum4( + *b"ARST", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + + impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -71,35 +96,13 @@ parameter_types! { pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; } -pub const DEFAULT_WRAPPED_CURRENCY2: CurrencyId = CurrencyId::AlphaNum4( - *b"USDT", - [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, - ], -); -pub const DEFAULT_WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( - *b"MXN\0", - [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, - ], -); - -pub const DEFAULT_WRAPPED_CURRENCY4: CurrencyId = CurrencyId::AlphaNum4( - *b"ARST", - [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, - ], -); impl Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; - type CurrencyId = CurrencyId; type StakeId = AccountId; + type CurrencyId = CurrencyId; type MaxRewardCurrencies = MaxRewardCurrencies; } diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index d6a378b7f..d34e2b4d3 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -32,7 +32,7 @@ pub use currency::Amount; pub use default_weights::{SubstrateWeight, WeightInfo}; pub use pallet::*; -use primitives::{BalanceToFixedPoint, StellarPublicKeyRaw, VaultCurrencyPair}; +use primitives::{StellarPublicKeyRaw, VaultCurrencyPair}; use crate::types::{ BalanceOf, CurrencyId, DefaultSystemVault, DefaultVaultCurrencyPair, RichSystemVault, @@ -105,10 +105,7 @@ pub mod pallet { + Default + Debug + TypeInfo - + MaxEncodedLen - + From<<::SignedFixedPoint as FixedPointNumber>::Inner> - + BalanceToFixedPoint<::SignedFixedPoint>; - + + MaxEncodedLen; /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; diff --git a/testchain/runtime/Cargo.toml b/testchain/runtime/Cargo.toml index 7eb066cbc..d9680ad24 100644 --- a/testchain/runtime/Cargo.toml +++ b/testchain/runtime/Cargo.toml @@ -61,6 +61,7 @@ fee = {path = "../../pallets/fee", default-features = false} issue = {path = "../../pallets/issue", default-features = false} nomination = {path = "../../pallets/nomination", default-features = false} oracle = {path = "../../pallets/oracle", default-features = false} +pooled-rewards = {path = "../../pallets/pooled-rewards", default-features = false} redeem = {path = "../../pallets/redeem", default-features = false} replace = {path = "../../pallets/replace", default-features = false} reward = {path = "../../pallets/reward", default-features = false} @@ -136,6 +137,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", + "pooled-rewards/std", "primitives/std", "redeem/std", "replace/std", diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index c8c80ad80..4911fc419 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -558,6 +558,7 @@ impl fee::Config for Runtime { impl nomination::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = nomination::SubstrateWeight; + type PoolRewards = PooledRewards; } impl clients_info::Config for Runtime { @@ -567,6 +568,19 @@ impl clients_info::Config for Runtime { type MaxUriLength = ConstU32<255>; } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type CurrencyId=CurrencyId; + type StakeId = AccountId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -600,6 +614,7 @@ construct_runtime! { Nomination: nomination::{Pallet, Call, Config, Storage, Event} = 28, DiaOracleModule: dia_oracle::{Pallet, Call, Config, Storage, Event} = 29, ClientsInfo: clients_info::{Pallet, Call, Storage, Event} = 30, + PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event} = 31, } } From 1a86542fbe34d7706d39a438cbecefaae42a10c4 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 10:04:54 -0300 Subject: [PATCH 03/52] rename CurrencyId type of pooled-reward pallet to avoid conflict with orml_currencies --- pallets/nomination/src/mock.rs | 4 +- pallets/pooled-rewards/src/lib.rs | 121 ++++++++++------------------- pallets/pooled-rewards/src/mock.rs | 9 +-- testchain/runtime/src/lib.rs | 2 +- 4 files changed, 45 insertions(+), 91 deletions(-) diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index 46f5ed2fb..470d36cc9 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -51,7 +51,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: reward::{Pallet, Call, Storage, Event}, - PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, + PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, @@ -299,7 +299,7 @@ impl pooled_rewards::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; - type CurrencyId= CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; type StakeId = AccountId; type MaxRewardCurrencies = MaxRewardCurrencies; } diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs index 2c0f3f4f7..323c82a02 100644 --- a/pallets/pooled-rewards/src/lib.rs +++ b/pallets/pooled-rewards/src/lib.rs @@ -35,12 +35,12 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, BoundedBTreeSet}; - + use frame_support::{pallet_prelude::*, BoundedBTreeSet}; + /// ## Configuration /// The pallet's configuration trait. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -53,24 +53,21 @@ pub mod pallet { + Decode + TypeInfo + MaxEncodedLen; - + /// The pool identifier type. - type PoolId: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaxEncodedLen; + type PoolId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; /// The stake identifier type. - type StakeId: Parameter + type StakeId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaxEncodedLen; + + /// The currency ID type. + type PoolRewardsCurrencyId: Parameter + Member + + Copy + MaybeSerializeDeserialize - + Debug + + Ord + MaxEncodedLen; - /// The currency ID type. - type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + MaxEncodedLen; - /// The maximum number of reward currencies. #[pallet::constant] type MaxRewardCurrencies: Get; @@ -86,7 +83,7 @@ pub mod pallet { amount: T::SignedFixedPoint, }, DistributeReward { - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, amount: T::SignedFixedPoint, }, WithdrawStake { @@ -97,7 +94,7 @@ pub mod pallet { WithdrawReward { pool_id: T::PoolId, stake_id: T::StakeId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, amount: T::SignedFixedPoint, }, } @@ -127,8 +124,13 @@ pub mod pallet { /// 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>; + pub type TotalRewards, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::PoolRewardsCurrencyId, + SignedFixedPoint, + ValueQuery, + >; /// Used to compute the rewards for a participant's stake. #[pallet::storage] @@ -136,7 +138,7 @@ pub mod pallet { pub type RewardPerToken, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::CurrencyId, + T::PoolRewardsCurrencyId, Blake2_128Concat, T::PoolId, SignedFixedPoint, @@ -158,7 +160,7 @@ pub mod pallet { pub type RewardTally, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::CurrencyId, + T::PoolRewardsCurrencyId, Blake2_128Concat, (T::PoolId, T::StakeId), SignedFixedPoint, @@ -171,7 +173,7 @@ pub mod pallet { _, Blake2_128Concat, T::PoolId, - BoundedBTreeSet, + BoundedBTreeSet, ValueQuery, >; @@ -233,7 +235,7 @@ impl, I: 'static> Pallet { } pub fn get_total_rewards( - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, ) -> Result<::Inner, DispatchError> { Ok(Self::total_rewards(currency_id) .truncate_to_inner() @@ -271,7 +273,7 @@ impl, I: 'static> Pallet { pub fn distribute_reward( pool_id: &T::PoolId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, reward: SignedFixedPoint, ) -> DispatchResult { if reward.is_zero() { @@ -299,7 +301,7 @@ impl, I: 'static> Pallet { pub fn compute_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, ) -> 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); @@ -352,7 +354,7 @@ impl, I: 'static> Pallet { pub fn withdraw_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, ) -> 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) @@ -381,14 +383,14 @@ pub trait RewardsApi where Balance: Saturating + PartialOrd + Copy, { - type CurrencyId; + type PoolRewardsCurrencyId; fn reward_currencies_len(pool_id: &PoolId) -> u32; /// Distribute the `amount` to all participants OR error if zero total stake. fn distribute_reward( pool_id: &PoolId, - currency_id: Self::CurrencyId, + currency_id: Self::PoolRewardsCurrencyId, amount: Balance, ) -> DispatchResult; @@ -396,14 +398,14 @@ where fn compute_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: Self::CurrencyId, + currency_id: Self::PoolRewardsCurrencyId, ) -> Result; /// Withdraw all rewards from the `stake_id`. fn withdraw_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: Self::CurrencyId, + currency_id: Self::PoolRewardsCurrencyId, ) -> Result; /// Deposit stake for an account. @@ -440,18 +442,6 @@ where } } -pub trait ModifyStakePool -where - Balance: Saturating + PartialOrd + Copy, -{ - /// 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; - -} - impl RewardsApi for Pallet where T: Config, @@ -459,7 +449,7 @@ where Balance: BalanceToFixedPoint> + Saturating + PartialOrd + Copy, ::Inner: TryInto, { - type CurrencyId = T::CurrencyId; + type PoolRewardsCurrencyId = T::PoolRewardsCurrencyId; fn reward_currencies_len(pool_id: &T::PoolId) -> u32 { RewardCurrencies::::get(pool_id).len() as u32 @@ -467,7 +457,7 @@ where fn distribute_reward( pool_id: &T::PoolId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, amount: Balance, ) -> DispatchResult { Pallet::::distribute_reward( @@ -480,7 +470,7 @@ where fn compute_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, ) -> Result { Pallet::::compute_reward(pool_id, stake_id, currency_id)? .try_into() @@ -490,7 +480,7 @@ where fn withdraw_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::CurrencyId, + currency_id: T::PoolRewardsCurrencyId, ) -> Result { Pallet::::withdraw_reward(pool_id, stake_id, currency_id)? .try_into() @@ -538,57 +528,24 @@ where } } -impl ModifyStakePool for Pallet -where - T: Config, - I: 'static, - Balance: BalanceToFixedPoint> + Saturating + PartialOrd + Copy, - -{ - 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 + Copy, { - type CurrencyId = (); + type PoolRewardsCurrencyId = (); fn reward_currencies_len(_: &PoolId) -> u32 { Default::default() } - fn distribute_reward(_: &PoolId, _: Self::CurrencyId, _: Balance) -> DispatchResult { + fn distribute_reward(_: &PoolId, _: Self::PoolRewardsCurrencyId, _: Balance) -> DispatchResult { Ok(()) } fn compute_reward( _: &PoolId, _: &StakeId, - _: Self::CurrencyId, + _: Self::PoolRewardsCurrencyId, ) -> Result { Ok(Default::default()) } @@ -596,7 +553,7 @@ where fn withdraw_reward( _: &PoolId, _: &StakeId, - _: Self::CurrencyId, + _: Self::PoolRewardsCurrencyId, ) -> Result { Ok(Default::default()) } diff --git a/pallets/pooled-rewards/src/mock.rs b/pallets/pooled-rewards/src/mock.rs index 603edb71e..cbe1908a3 100644 --- a/pallets/pooled-rewards/src/mock.rs +++ b/pallets/pooled-rewards/src/mock.rs @@ -14,7 +14,6 @@ pub use primitives::{CurrencyId, VaultCurrencyPair, VaultId}; use crate as pooled_rewards; use crate::{Config, Error}; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -59,12 +58,11 @@ pub const DEFAULT_WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( pub const DEFAULT_WRAPPED_CURRENCY4: CurrencyId = CurrencyId::AlphaNum4( *b"ARST", [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + 44, 123, 1, 49, 176, 55, 23, 123, 171, 123, 54, 155, 16, 50, 30, 226, 155, 231, 46, 199, 1, + 11, 4, 144, 240, 123, 51, 33, 72, 34, 159, 33, ], ); - impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -96,13 +94,12 @@ parameter_types! { pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; } - impl Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; type StakeId = AccountId; - type CurrencyId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; type MaxRewardCurrencies = MaxRewardCurrencies; } diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index 4911fc419..b4d93dbe9 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -576,7 +576,7 @@ impl pooled_rewards::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; - type CurrencyId=CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; type StakeId = AccountId; type MaxRewardCurrencies = MaxRewardCurrencies; } From af57fd379bf933f96a78abb296ffdcdb3f5c2622 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 10:12:33 -0300 Subject: [PATCH 04/52] remove tight coupling of pallet pooled rewards to nomination --- pallets/nomination/src/lib.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index d0ef09a34..91d3fabec 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -41,22 +41,16 @@ pub(crate) type DefaultVaultId = #[frame_support::pallet] pub mod pallet { use super::*; + use currency::CurrencyId; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use vault_registry::types::DefaultVaultCurrencyPair; - use currency::{CurrencyId}; /// ## Configuration /// The pallet's configuration trait. - /// - /// TODO is it okay to do tight coupling also for pooled-rewards?? #[pallet::config] pub trait Config: - frame_system::Config - + security::Config - + vault_registry::Config - + fee::Config - + pooled_rewards::Config + frame_system::Config + security::Config + vault_registry::Config + fee::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -64,8 +58,11 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - type PoolRewards: pooled_rewards::RewardsApi, Self::AccountId, BalanceOf>; - + type PoolRewards: pooled_rewards::RewardsApi< + CurrencyId, + Self::AccountId, + BalanceOf, + >; } #[pallet::event] @@ -256,7 +253,7 @@ impl Pallet { // withdraw `amount` of stake from the vault staking pool ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; - //withdraw from the pooled reward + //withdraw from the pooled reward ext::pooled_rewards::withdraw_stake::( &vault_id.collateral_currency(), nominator_id, From 9121dfa771f4976d6d419dd73833d0297d839734 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 10:14:51 -0300 Subject: [PATCH 05/52] remove coupling of vault registry to new pallet pooled rewards --- pallets/vault-registry/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index d34e2b4d3..9c68ce317 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -81,7 +81,6 @@ pub mod pallet { + security::Config + currency::Config> + fee::Config, SignedInner = SignedInner> - + pooled_rewards::Config { /// The vault module id, used for deriving its sovereign account ID. #[pallet::constant] // put the constant in metadata From 1927a9809b02bbba0c86ebb48d2b2a084d3eee19 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 10:27:39 -0300 Subject: [PATCH 06/52] format --- pallets/nomination/src/ext.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index 35d2244a0..e372594b5 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -119,7 +119,7 @@ pub(crate) mod staking { #[cfg_attr(test, mockable)] pub(crate) mod pooled_rewards { - use currency::{CurrencyId, Amount}; + use currency::{Amount, CurrencyId}; use frame_support::dispatch::DispatchResult; use pooled_rewards::RewardsApi; @@ -127,17 +127,15 @@ pub(crate) mod pooled_rewards { currency_id: &CurrencyId, account_id: &::AccountId, amount: Amount, - ) -> DispatchResult - { - T::PoolRewards::deposit_stake(currency_id, account_id,amount.amount()) + ) -> DispatchResult { + T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) } pub fn withdraw_stake( currency_id: &CurrencyId, account_id: &::AccountId, amount: Amount, - ) -> DispatchResult - { - T::PoolRewards::withdraw_stake(currency_id, account_id,amount.amount()) + ) -> DispatchResult { + T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) } } From 27564212ecf2f94daa59bc0c683488096b163d7b Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 11:44:23 -0300 Subject: [PATCH 07/52] missing implementation of new pallet pooled-reward in replace mock --- Cargo.lock | 1 + pallets/nomination/Cargo.toml | 1 + pallets/replace/Cargo.toml | 2 ++ pallets/replace/src/mock.rs | 15 ++++++++++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 22cda9709..430e09c84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6778,6 +6778,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", + "pooled-rewards", "reward", "scale-info", "security", diff --git a/pallets/nomination/Cargo.toml b/pallets/nomination/Cargo.toml index 875effa6a..1479c83b6 100644 --- a/pallets/nomination/Cargo.toml +++ b/pallets/nomination/Cargo.toml @@ -66,6 +66,7 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", + "pooled-rewards/std", "currency/std", "security/std", "vault-registry/std", diff --git a/pallets/replace/Cargo.toml b/pallets/replace/Cargo.toml index 07734fad3..763669a22 100644 --- a/pallets/replace/Cargo.toml +++ b/pallets/replace/Cargo.toml @@ -52,6 +52,7 @@ currency = { path = "../currency", default-features = false, features = ["testin stellar-relay = { path = "../stellar-relay", features = ["testing-utils"] } security = { path = "../security", features = ['testing-utils'] } oracle = { path = "../oracle", features = ['testing-utils'] } +pooled-rewards = { path = "../pooled-rewards", default-features = false } # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40" } @@ -73,6 +74,7 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", + "pooled-rewards/std", "currency/std", "fee/std", "nomination/std", diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index f54e36b83..aaedd5469 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -52,7 +52,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: reward::{Pallet, Call, Storage, Event}, - + PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event}, Security: security::{Pallet, Call, Storage, Event}, @@ -279,9 +279,22 @@ impl security::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = (); } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { + type RuntimeEvent = TestEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = AccountId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} impl nomination::Config for Test { type RuntimeEvent = TestEvent; + type PoolRewards = PooledRewards; type WeightInfo = nomination::SubstrateWeight; } From 9e95a0be5647ceb325754d5de90bc1f3b2b020ba Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 17:40:23 -0300 Subject: [PATCH 08/52] call reward pool in vault-registry --- pallets/vault-registry/Cargo.toml | 1 + pallets/vault-registry/src/ext.rs | 24 ++++++++++++++++++++++++ pallets/vault-registry/src/lib.rs | 28 ++++++++++++++++++++++++++-- pallets/vault-registry/src/types.rs | 18 ++++++++++++++++-- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/pallets/vault-registry/Cargo.toml b/pallets/vault-registry/Cargo.toml index 26e66b5ba..cf0ea34aa 100644 --- a/pallets/vault-registry/Cargo.toml +++ b/pallets/vault-registry/Cargo.toml @@ -80,6 +80,7 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", + "pooled-rewards/std", "orml-currencies/std", "orml-tokens/std", "orml-traits/std", diff --git a/pallets/vault-registry/src/ext.rs b/pallets/vault-registry/src/ext.rs index 11d358505..f84f525b7 100644 --- a/pallets/vault-registry/src/ext.rs +++ b/pallets/vault-registry/src/ext.rs @@ -98,3 +98,27 @@ pub(crate) mod reward { T::VaultRewards::get_stake(vault_id) } } + +#[cfg_attr(test, mockable)] +pub(crate) mod pooled_rewards { + + use currency::{Amount, CurrencyId}; + use frame_support::dispatch::DispatchResult; + use pooled_rewards::RewardsApi; + + pub fn deposit_stake( + currency_id: &CurrencyId, + account_id: &::AccountId, + amount: Amount, + ) -> DispatchResult { + T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) + } + + pub fn withdraw_stake( + currency_id: &CurrencyId, + account_id: &::AccountId, + amount: Amount, + ) -> DispatchResult { + T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) + } +} diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 9c68ce317..e0d3865de 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -108,6 +108,13 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; + //Pool rewards interface + type PoolRewards: pooled_rewards::RewardsApi< + CurrencyId, + Self::AccountId, + BalanceOf, + >; + /// Currency used for griefing collateral, e.g. DOT. #[pallet::constant] type GetGriefingCollateralCurrencyId: Get>; @@ -919,7 +926,13 @@ impl Pallet { amount.lock_on(&vault_id.account_id)?; // Deposit `amount` of stake in the pool - ext::staking::deposit_stake::(vault_id, &vault_id.account_id, amount)?; + ext::staking::deposit_stake::(vault_id, &vault_id.account_id, &amount.clone())?; + + ext::pooled_rewards::deposit_stake::( + &vault_id.collateral_currency(), + &vault_id.account_id, + amount.clone(), + )?; Ok(()) } @@ -938,7 +951,12 @@ impl Pallet { Self::decrease_total_backing_collateral(&vault_id.currencies, amount)?; // Withdraw `amount` of stake from the pool - ext::staking::withdraw_stake::(vault_id, &vault_id.account_id, amount)?; + ext::staking::withdraw_stake::(vault_id, &vault_id.account_id, &amount.clone())?; + ext::pooled_rewards::withdraw_stake::( + &vault_id.collateral_currency(), + &vault_id.account_id, + amount.clone(), + )?; Ok(()) } @@ -1513,6 +1531,12 @@ impl Pallet { &old_vault_id.account_id, &to_be_released, )?; + + ext::pooled_rewards::deposit_stake::( + &old_vault_id.collateral_currency(), + &old_vault_id.account_id, + to_be_released, + )?; } old_vault.execute_redeem_tokens(tokens)?; diff --git a/pallets/vault-registry/src/types.rs b/pallets/vault-registry/src/types.rs index 9f2c7b4aa..806f81b1b 100644 --- a/pallets/vault-registry/src/types.rs +++ b/pallets/vault-registry/src/types.rs @@ -545,7 +545,14 @@ impl RichVault { pub(crate) fn slash_for_to_be_redeemed(&mut self, amount: &Amount) -> DispatchResult { let vault_id = self.id(); let collateral = self.get_vault_collateral()?.min(amount)?; - ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &collateral)?; + ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &collateral.clone())?; + + ext::pooled_rewards::withdraw_stake::( + &vault_id.collateral_currency(), + &vault_id.account_id, + collateral.clone(), + )?; + self.increase_liquidated_collateral(&collateral)?; Ok(()) } @@ -561,7 +568,14 @@ impl RichVault { .unwrap_or((amount.clone(), None)); // "slash" vault first - ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &to_withdraw)?; + ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &to_withdraw.clone())?; + + ext::pooled_rewards::withdraw_stake::( + &vault_id.collateral_currency(), + &vault_id.account_id, + to_withdraw, + )?; + // take remainder from nominators if let Some(to_slash) = to_slash { ext::staking::slash_stake::(&vault_id, &to_slash)?; From 112659f65646bba4650f526da8b54cc2a5b1705d Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 10 Oct 2023 17:50:11 -0300 Subject: [PATCH 09/52] added pooled-rewards to vault-registry mock runtime --- pallets/vault-registry/src/mock.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index c0460f667..6506ca63f 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -49,6 +49,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: reward::{Pallet, Call, Storage, Event}, + PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, @@ -257,6 +258,19 @@ impl fee::Config for Test { type MaxExpectedValue = MaxExpectedValue; } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { + type RuntimeEvent = TestEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = AccountId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + parameter_types! { pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg"); } @@ -266,6 +280,7 @@ impl Config for Test { type RuntimeEvent = TestEvent; type Balance = Balance; type WeightInfo = vault_registry::SubstrateWeight; + type PoolRewards = PooledRewards; type GetGriefingCollateralCurrencyId = GetNativeCurrencyId; } From 4b4bfd5fae65c2bcc11da1415ebc69e553e37ee6 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 11 Oct 2023 18:35:35 -0300 Subject: [PATCH 10/52] implementing only one reward pallet --- Cargo.lock | 8 ++-- Cargo.toml | 1 - pallets/currency/src/lib.rs | 3 +- pallets/fee/Cargo.toml | 5 ++- pallets/fee/src/lib.rs | 59 +++++++++++++++++++++++------ pallets/fee/src/mock.rs | 21 +++++++--- pallets/issue/Cargo.toml | 2 + pallets/issue/src/mock.rs | 28 +++++++++----- pallets/nomination/Cargo.toml | 6 +-- pallets/nomination/src/ext.rs | 46 +++++++++++----------- pallets/nomination/src/lib.rs | 20 ---------- pallets/nomination/src/mock.rs | 19 ++++------ pallets/oracle/src/lib.rs | 10 +++++ pallets/pooled-rewards/Cargo.toml | 4 +- pallets/pooled-rewards/src/lib.rs | 55 ++++++++++++++++++++------- pallets/redeem/Cargo.toml | 3 +- pallets/redeem/src/mock.rs | 19 +++++++--- pallets/replace/Cargo.toml | 5 ++- pallets/replace/src/mock.rs | 19 ++++------ pallets/vault-registry/src/ext.rs | 55 +++++++++++++-------------- pallets/vault-registry/src/lib.rs | 24 +----------- pallets/vault-registry/src/mock.rs | 21 +++++----- pallets/vault-registry/src/types.rs | 16 +------- testchain/runtime/src/lib.rs | 17 +++------ 24 files changed, 249 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 430e09c84..28a5bf8aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2155,7 +2155,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", - "reward", + "pooled-rewards", "scale-info", "security", "serde", @@ -3485,6 +3485,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", + "pooled-rewards", "reward", "scale-info", "security", @@ -5131,7 +5132,6 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", - "reward", "scale-info", "security", "serde", @@ -6043,7 +6043,6 @@ name = "pooled-rewards" version = "1.0.0" dependencies = [ "currency", - "fee", "frame-benchmarking", "frame-support", "frame-system", @@ -6617,7 +6616,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", - "reward", + "pooled-rewards", "scale-info", "security", "serde", @@ -6779,7 +6778,6 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", - "reward", "scale-info", "security", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3715cce6f..d5bdb525f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "pallets/nomination", "pallets/oracle", "pallets/pooled-rewards", - "pallets/reward", "pallets/staking", "pallets/stellar-relay", "pallets/vault-registry", diff --git a/pallets/currency/src/lib.rs b/pallets/currency/src/lib.rs index dfe4dede1..58d366c2d 100644 --- a/pallets/currency/src/lib.rs +++ b/pallets/currency/src/lib.rs @@ -88,7 +88,8 @@ pub mod pallet { + FullCodec + Copy + Default - + Debug; + + Debug + +From; /// Relay chain currency e.g. DOT/KSM #[pallet::constant] diff --git a/pallets/fee/Cargo.toml b/pallets/fee/Cargo.toml index 9152cb68c..cdf5f66b6 100644 --- a/pallets/fee/Cargo.toml +++ b/pallets/fee/Cargo.toml @@ -25,10 +25,11 @@ pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "p # Parachain dependencies currency = { path = "../currency", default-features = false } security = { path = "../security", default-features = false } -reward = { path = "../reward", default-features = false } +pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } + [dev-dependencies] mocktopus = "0.8.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } @@ -58,9 +59,9 @@ std = [ "pallet-balances/std", "currency/std", "security/std", - "reward/std", "staking/std", "primitives/std", + "pooled-rewards/std", "orml-currencies/std", "orml-tokens/std", "orml-traits/std", diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index c387be3c9..7ff5b56d8 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -15,7 +15,7 @@ use frame_support::{ }; #[cfg(test)] use mocktopus::macros::mockable; -use sp_arithmetic::{traits::*, FixedPointNumber, FixedPointOperand}; +use sp_arithmetic::{traits::*, FixedPointNumber, FixedPointOperand, Perquintill}; use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned}; use sp_std::{ convert::{TryFrom, TryInto}, @@ -25,9 +25,10 @@ use sp_std::{ use currency::{Amount, CurrencyId, OnSweep}; pub use default_weights::{SubstrateWeight, WeightInfo}; pub use pallet::*; -use reward::Rewards; +pub use pooled_rewards::RewardsApi; use types::{BalanceOf, DefaultVaultId, SignedFixedPoint, UnsignedFixedPoint}; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -57,6 +58,7 @@ pub mod pallet { UnsignedFixedPoint = UnsignedFixedPoint, SignedFixedPoint = SignedFixedPoint, > + { /// The fee module id, used for deriving its sovereign account ID. #[pallet::constant] @@ -101,7 +103,12 @@ pub mod pallet { + TypeInfo; /// Vault reward pool. - type VaultRewards: reward::Rewards, BalanceOf, CurrencyId>; + type VaultRewards: pooled_rewards::RewardsApi< + CurrencyId, + DefaultVaultId, + BalanceOf, + CurrencyId, + >; /// Vault staking pool. type VaultStaking: staking::Staking< @@ -115,6 +122,9 @@ pub mod pallet { /// Handler to transfer undistributed rewards. type OnSweep: OnSweep>; + //currency to usd interface + type BaseCurrency: Get>; + /// Maximum expected value to set the storage fields to. #[pallet::constant] type MaxExpectedValue: Get>; @@ -126,6 +136,7 @@ pub mod pallet { TryIntoIntError, /// Value exceeds the expected upper bound for storage fields in this pallet. AboveMaxExpectedValue, + Overflow, } #[pallet::hooks] @@ -459,10 +470,9 @@ impl Pallet { } // Private functions internal to this pallet - /// Withdraw rewards from a pool and transfer to `account_id`. fn withdraw_from_reward_pool< - Rewards: reward::Rewards, BalanceOf, CurrencyId>, + Rewards: pooled_rewards::RewardsApi, DefaultVaultId, BalanceOf, CurrencyId>, Staking: staking::Staking, T::AccountId, T::Index, BalanceOf, CurrencyId>, >( vault_id: &DefaultVaultId, @@ -480,21 +490,46 @@ impl Pallet { } fn distribute(reward: &Amount) -> Result, DispatchError> { - Ok(if T::VaultRewards::distribute_reward(reward.amount(), reward.currency()).is_err() { - reward.clone() - } else { - Amount::::zero(reward.currency()) - }) + + //fetch total stake (all), and calulate total usd stake across pools + //distribute the rewards into each reward pool for each collateral, + //taking into account it's value in usd + + let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; + let total_stake_in_usd= BalanceOf::::default(); + for (currency_id, stake) in total_stakes.clone().into_iter(){ + + let stake_in_amount = Amount::::new(stake,currency_id); + let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; + total_stake_in_usd.checked_add(&stake_in_usd.amount()).ok_or(Error::::Overflow)?; + } + + let error_reward_accum = Amount::::zero(reward.currency()); + + for (currency_id, stake) in total_stakes.into_iter(){ + + let stake_in_amount = Amount::::new(stake,currency_id); + let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; + let percentage = Perquintill::from_rational(stake_in_usd.amount(),total_stake_in_usd); + + //TODO multiply with floor or ceil? + let reward_for_pool = percentage.mul_floor(reward.amount()); + + if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(),reward_for_pool).is_err() { + error_reward_accum.checked_add(&reward)?; + } + } + Ok(error_reward_accum) } pub fn distribute_from_reward_pool< - Rewards: reward::Rewards, BalanceOf, CurrencyId>, + Rewards: pooled_rewards::RewardsApi, DefaultVaultId, BalanceOf, CurrencyId>, Staking: staking::Staking, T::AccountId, T::Index, BalanceOf, CurrencyId>, >( vault_id: &DefaultVaultId, ) -> DispatchResult { for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { - let reward = Rewards::withdraw_reward(vault_id, currency_id)?; + let reward = Rewards::withdraw_reward(&vault_id.collateral_currency(), &vault_id, currency_id)?; Staking::distribute_reward(vault_id, reward, currency_id)?; } diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index 9685a50d7..2ac0e957b 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -41,7 +41,7 @@ frame_support::construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, - Rewards: reward::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, Staking: staking::{Pallet, Storage, Event}, // Operational @@ -161,12 +161,17 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } -impl reward::Config for Test { +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = VaultId; + type MaxRewardCurrencies = MaxRewardCurrencies; } impl staking::Config for Test { @@ -216,11 +221,16 @@ impl currency::Config for Test { type AmountCompatibility = primitives::StellarCompatibility; } + +const USD: CurrencyId = CurrencyId::XCM(2); + parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = USD; } + impl Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -232,6 +242,7 @@ impl Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type BaseCurrency = GetBaseCurrency; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/issue/Cargo.toml b/pallets/issue/Cargo.toml index 1d175ce66..771b59e38 100644 --- a/pallets/issue/Cargo.toml +++ b/pallets/issue/Cargo.toml @@ -35,6 +35,7 @@ oracle = { path = "../oracle", default-features = false } security = { path = "../security", default-features = false } stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } +pooled-rewards = { path = "../pooled-rewards", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -76,6 +77,7 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", + "pooled-rewards/std", "currency/std", "fee/std", "oracle/std", diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 042d90356..26a44dc97 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -60,7 +60,7 @@ frame_support::construct_runtime!( Oracle: oracle::{Pallet, Call, Config, Storage, Event}, Fee: fee::{Pallet, Call, Config, Storage}, Staking: staking::{Pallet, Storage, Event}, - Rewards: reward::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, } ); @@ -264,14 +264,6 @@ impl security::Config for Test { type WeightInfo = (); } -impl reward::Config for Test { - type RuntimeEvent = TestEvent; - type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; -} - impl staking::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; @@ -294,9 +286,13 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } + +const USD: CurrencyId = CurrencyId::XCM(2); + parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -308,10 +304,24 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; + type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { + type RuntimeEvent = TestEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = VaultId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + parameter_types! { pub const MinimumPeriod: Moment = 5; } diff --git a/pallets/nomination/Cargo.toml b/pallets/nomination/Cargo.toml index 1479c83b6..09b449a6b 100644 --- a/pallets/nomination/Cargo.toml +++ b/pallets/nomination/Cargo.toml @@ -28,9 +28,8 @@ security = { path = "../security", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } fee = { path = "../fee", default-features = false } oracle = { path = "../oracle", default-features = false } -reward = { path = "../reward", default-features = false } -staking = { path = "../staking", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } +staking = { path = "../staking", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -66,12 +65,11 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", - "pooled-rewards/std", "currency/std", "security/std", "vault-registry/std", "fee/std", - "reward/std", + "pooled-rewards/std", "oracle/std", "staking/std", "primitives/std", diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index e372594b5..5dd811205 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -116,26 +116,26 @@ pub(crate) mod staking { } } -#[cfg_attr(test, mockable)] -pub(crate) mod pooled_rewards { - - use currency::{Amount, CurrencyId}; - use frame_support::dispatch::DispatchResult; - use pooled_rewards::RewardsApi; - - pub fn deposit_stake( - currency_id: &CurrencyId, - account_id: &::AccountId, - amount: Amount, - ) -> DispatchResult { - T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) - } - - pub fn withdraw_stake( - currency_id: &CurrencyId, - account_id: &::AccountId, - amount: Amount, - ) -> DispatchResult { - T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) - } -} +// #[cfg_attr(test, mockable)] +// pub(crate) mod pooled_rewards { + +// use currency::{Amount, CurrencyId}; +// use frame_support::dispatch::DispatchResult; +// use pooled_rewards::RewardsApi; + +// pub fn deposit_stake( +// currency_id: &CurrencyId, +// account_id: &::AccountId, +// amount: Amount, +// ) -> DispatchResult { +// T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) +// } + +// pub fn withdraw_stake( +// currency_id: &CurrencyId, +// account_id: &::AccountId, +// amount: Amount, +// ) -> DispatchResult { +// T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) +// } +// } diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index 91d3fabec..a993bbe32 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -41,7 +41,6 @@ pub(crate) type DefaultVaultId = #[frame_support::pallet] pub mod pallet { use super::*; - use currency::CurrencyId; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use vault_registry::types::DefaultVaultCurrencyPair; @@ -58,11 +57,6 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - type PoolRewards: pooled_rewards::RewardsApi< - CurrencyId, - Self::AccountId, - BalanceOf, - >; } #[pallet::event] @@ -253,13 +247,6 @@ impl Pallet { // withdraw `amount` of stake from the vault staking pool ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; - //withdraw from the pooled reward - ext::pooled_rewards::withdraw_stake::( - &vault_id.collateral_currency(), - nominator_id, - amount.clone(), - )?; - amount.unlock_on(&vault_id.account_id)?; amount.transfer(&vault_id.account_id, nominator_id)?; @@ -302,13 +289,6 @@ impl Pallet { amount.lock_on(&vault_id.account_id)?; ext::vault_registry::try_increase_total_backing_collateral(&vault_id.currencies, &amount)?; - // deposit into pool rewards - ext::pooled_rewards::deposit_stake::( - &vault_id.collateral_currency(), - nominator_id, - amount.clone(), - )?; - Self::deposit_event(Event::::DepositCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index 470d36cc9..fda1ee6ab 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -50,8 +50,7 @@ frame_support::construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, - Rewards: reward::{Pallet, Call, Storage, Event}, - PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, @@ -178,13 +177,7 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } -impl reward::Config for Test { - type RuntimeEvent = TestEvent; - type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; -} + parameter_types! { pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg"); @@ -259,11 +252,15 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } +const USD: CurrencyId = CurrencyId::XCM(2); + parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = USD; } + impl fee::Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -275,6 +272,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type BaseCurrency = GetBaseCurrency; } impl oracle::Config for Test { @@ -300,14 +298,13 @@ impl pooled_rewards::Config for Test { type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; type PoolRewardsCurrencyId = CurrencyId; - type StakeId = AccountId; + type StakeId = VaultId; type MaxRewardCurrencies = MaxRewardCurrencies; } impl Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = crate::SubstrateWeight; - type PoolRewards = PooledRewards; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index cd1a3ee4b..ef863161f 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -355,3 +355,13 @@ impl Pallet { T::DataProvider::get_no_op(key) } } + + +// pub trait CurrencyToUsd +// { +// pub fn currency_to_usd( +// amount: BalanceOf, +// currency_id: CurrencyId, +// )-> Result, DispatchError>; + +// } \ No newline at end of file diff --git a/pallets/pooled-rewards/Cargo.toml b/pallets/pooled-rewards/Cargo.toml index f9c8e4987..358a17b94 100644 --- a/pallets/pooled-rewards/Cargo.toml +++ b/pallets/pooled-rewards/Cargo.toml @@ -13,7 +13,7 @@ scale-info = { version = "2.0.0", default-features = false, features = ["derive" # Parachain dependencies primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } -fee = { path = "../fee", default-features = false } + # Substrate dependencies @@ -22,6 +22,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot- sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +currency = { path = "../currency", default-features = false, features = ["testing-constants"] } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } @@ -40,7 +41,6 @@ std = [ "serde", "codec/std", "currency/std", - "fee/std", "primitives/std", "sp-arithmetic/std", "sp-core/std", diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs index 323c82a02..c5ba4f9f0 100644 --- a/pallets/pooled-rewards/src/lib.rs +++ b/pallets/pooled-rewards/src/lib.rs @@ -26,7 +26,9 @@ use sp_runtime::{ }, ArithmeticError, }; -use sp_std::{cmp::PartialOrd, convert::TryInto, fmt::Debug}; + +//use currency::Amount; +use sp_std::{cmp::PartialOrd, convert::TryInto, fmt::Debug, vec::Vec}; pub(crate) type SignedFixedPoint = >::SignedFixedPoint; @@ -377,20 +379,22 @@ impl, I: 'static> Pallet { }); Ok(reward) } + + } -pub trait RewardsApi +pub trait RewardsApi where Balance: Saturating + PartialOrd + Copy, { - type PoolRewardsCurrencyId; + //type PoolRewardsCurrencyId = CurrencyId; fn reward_currencies_len(pool_id: &PoolId) -> u32; /// Distribute the `amount` to all participants OR error if zero total stake. fn distribute_reward( pool_id: &PoolId, - currency_id: Self::PoolRewardsCurrencyId, + currency_id: CurrencyId, amount: Balance, ) -> DispatchResult; @@ -398,14 +402,14 @@ where fn compute_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: Self::PoolRewardsCurrencyId, + currency_id: CurrencyId, ) -> Result; /// Withdraw all rewards from the `stake_id`. fn withdraw_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: Self::PoolRewardsCurrencyId, + currency_id: CurrencyId, ) -> Result; /// Deposit stake for an account. @@ -440,16 +444,21 @@ where Ok(()) } } + + //get total stake for each `pool_id` in the pallet + fn get_total_stake_all_pools()-> Result, DispatchError>; + + } -impl RewardsApi for Pallet +impl RewardsApi for Pallet where T: Config, I: 'static, Balance: BalanceToFixedPoint> + Saturating + PartialOrd + Copy, ::Inner: TryInto, { - type PoolRewardsCurrencyId = T::PoolRewardsCurrencyId; + //type PoolRewardsCurrencyId = CurrencyId; fn reward_currencies_len(pool_id: &T::PoolId) -> u32 { RewardCurrencies::::get(pool_id).len() as u32 @@ -457,7 +466,7 @@ where fn distribute_reward( pool_id: &T::PoolId, - currency_id: T::PoolRewardsCurrencyId, + currency_id:T::PoolRewardsCurrencyId, amount: Balance, ) -> DispatchResult { Pallet::::distribute_reward( @@ -526,26 +535,40 @@ where amount.to_fixed().ok_or(Error::::TryIntoIntError)?, ) } + + fn get_total_stake_all_pools()-> Result, DispatchError>{ + let mut pool_vec: Vec<(T::PoolId,Balance)> = Vec::new(); + for (pool_id, pool_total_stake) in TotalStake::::iter(){ + + let pool_stake_as_balance: Balance = pool_total_stake.truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError)?; + + pool_vec.push((pool_id,pool_stake_as_balance)); + } + return Ok(pool_vec); + } } -impl RewardsApi for () +impl RewardsApi for () where Balance: Saturating + PartialOrd + Default + Copy, { - type PoolRewardsCurrencyId = (); + //type PoolRewardsCurrencyId = (); fn reward_currencies_len(_: &PoolId) -> u32 { Default::default() } - fn distribute_reward(_: &PoolId, _: Self::PoolRewardsCurrencyId, _: Balance) -> DispatchResult { + fn distribute_reward(_: &PoolId, _: CurrencyId, _: Balance) -> DispatchResult { Ok(()) } fn compute_reward( _: &PoolId, _: &StakeId, - _: Self::PoolRewardsCurrencyId, + _: CurrencyId, ) -> Result { Ok(Default::default()) } @@ -553,7 +576,7 @@ where fn withdraw_reward( _: &PoolId, _: &StakeId, - _: Self::PoolRewardsCurrencyId, + _: CurrencyId, ) -> Result { Ok(Default::default()) } @@ -573,4 +596,8 @@ where fn withdraw_stake(_: &PoolId, _: &StakeId, _: Balance) -> DispatchResult { Ok(()) } + + fn get_total_stake_all_pools()-> Result, DispatchError>{ + Ok(Default::default()) + } } diff --git a/pallets/redeem/Cargo.toml b/pallets/redeem/Cargo.toml index 97a345318..80c29e65a 100644 --- a/pallets/redeem/Cargo.toml +++ b/pallets/redeem/Cargo.toml @@ -46,7 +46,7 @@ mocktopus = "0.8.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40" } # Parachain dependencies -reward = { path = "../reward" } +pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking" } currency = { path = "../currency", default-features = false, features = ["testing-constants", "testing-utils"] } stellar-relay = { path = "../stellar-relay", features = ["testing-utils"] } @@ -73,6 +73,7 @@ std = [ "frame-benchmarking/std", "pallet-balances/std", "pallet-timestamp/std", + "pooled-rewards/std", "currency/std", "fee/std", "oracle/std", diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index c204c33af..a50f6ba5a 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -53,7 +53,7 @@ frame_support::construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, - Rewards: reward::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event}, @@ -257,12 +257,17 @@ impl staking::Config for Test { type GetNativeCurrencyId = GetNativeCurrencyId; } -impl reward::Config for Test { +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = VaultId; + type MaxRewardCurrencies = MaxRewardCurrencies; } parameter_types! { @@ -312,9 +317,12 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } +const USD: CurrencyId = CurrencyId::XCM(2); + parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -326,6 +334,7 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; + type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } diff --git a/pallets/replace/Cargo.toml b/pallets/replace/Cargo.toml index 763669a22..81f319612 100644 --- a/pallets/replace/Cargo.toml +++ b/pallets/replace/Cargo.toml @@ -33,6 +33,7 @@ primitives = { package = "spacewalk-primitives", path = "../../primitives", defa security = { path = "../security", default-features = false } stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } +pooled-rewards = { path = "../pooled-rewards", default-features = false } substrate-stellar-sdk = { git = "https://github.com/pendulum-chain/substrate-stellar-sdk", branch = "polkadot-v0.9.40", default-features = false, features = ['offchain'] } @@ -46,13 +47,13 @@ mocktopus = "0.8.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40" } # Parachain dependencies -reward = { path = "../reward" } +pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking" } currency = { path = "../currency", default-features = false, features = ["testing-constants"] } stellar-relay = { path = "../stellar-relay", features = ["testing-utils"] } security = { path = "../security", features = ['testing-utils'] } oracle = { path = "../oracle", features = ['testing-utils'] } -pooled-rewards = { path = "../pooled-rewards", default-features = false } + # Orml dependencies orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40" } diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index aaedd5469..89b5f046d 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -51,8 +51,8 @@ frame_support::construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, - Rewards: reward::{Pallet, Call, Storage, Event}, - PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + // Operational StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event}, Security: security::{Pallet, Call, Storage, Event}, @@ -175,13 +175,7 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } -impl reward::Config for Test { - type RuntimeEvent = TestEvent; - type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; -} + parameter_types! { pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg"); @@ -288,13 +282,12 @@ impl pooled_rewards::Config for Test { type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; type PoolRewardsCurrencyId = CurrencyId; - type StakeId = AccountId; + type StakeId = VaultId; type MaxRewardCurrencies = MaxRewardCurrencies; } impl nomination::Config for Test { type RuntimeEvent = TestEvent; - type PoolRewards = PooledRewards; type WeightInfo = nomination::SubstrateWeight; } @@ -323,9 +316,12 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } +const USD: CurrencyId = CurrencyId::XCM(2); + parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -337,6 +333,7 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; + type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } diff --git a/pallets/vault-registry/src/ext.rs b/pallets/vault-registry/src/ext.rs index f84f525b7..1b3a970e1 100644 --- a/pallets/vault-registry/src/ext.rs +++ b/pallets/vault-registry/src/ext.rs @@ -75,50 +75,47 @@ pub(crate) mod staking { } } + #[cfg_attr(test, mockable)] -pub(crate) mod reward { - use frame_support::dispatch::DispatchError; +pub(crate) mod pooled_rewards { use currency::Amount; - use reward::Rewards; + use frame_support::dispatch::DispatchError; + use pooled_rewards::RewardsApi; + use sp_core::Get; use crate::DefaultVaultId; + // pub fn deposit_stake( + // currency_id: &CurrencyId, + // vault_id: &DefaultVaultId, + // amount: Amount, + // ) -> DispatchResult { + // T::VaultRewards::deposit_stake(currency_id, vault_id, amount.amount()) + // } + + // pub fn withdraw_stake( + // currency_id: &CurrencyId, + // vault_id: &DefaultVaultId, + // amount: Amount, + // ) -> DispatchResult { + // T::VaultRewards::withdraw_stake(currency_id, vault_id, amount.amount()) + // } + pub fn set_stake( vault_id: &DefaultVaultId, amount: &Amount, ) -> Result<(), DispatchError> { - T::VaultRewards::set_stake(vault_id, amount.amount(), amount.currency()) + //we need to normalize the amount into base currency, to handle vaults with same collateral + //but different issue tokens. + let stake = amount.convert_to(::BaseCurrency::get())?; + T::VaultRewards::set_stake(&vault_id.collateral_currency(), &vault_id, stake.amount()) } #[cfg(feature = "integration-tests")] pub fn get_stake( vault_id: &DefaultVaultId, ) -> Result, DispatchError> { - T::VaultRewards::get_stake(vault_id) - } -} - -#[cfg_attr(test, mockable)] -pub(crate) mod pooled_rewards { - - use currency::{Amount, CurrencyId}; - use frame_support::dispatch::DispatchResult; - use pooled_rewards::RewardsApi; - - pub fn deposit_stake( - currency_id: &CurrencyId, - account_id: &::AccountId, - amount: Amount, - ) -> DispatchResult { - T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) - } - - pub fn withdraw_stake( - currency_id: &CurrencyId, - account_id: &::AccountId, - amount: Amount, - ) -> DispatchResult { - T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) + T::VaultRewards::get_stake(&vault_id.collateral_currency(), &vault_id) } } diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index e0d3865de..491a75681 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -108,12 +108,6 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - //Pool rewards interface - type PoolRewards: pooled_rewards::RewardsApi< - CurrencyId, - Self::AccountId, - BalanceOf, - >; /// Currency used for griefing collateral, e.g. DOT. #[pallet::constant] @@ -928,12 +922,6 @@ impl Pallet { // Deposit `amount` of stake in the pool ext::staking::deposit_stake::(vault_id, &vault_id.account_id, &amount.clone())?; - ext::pooled_rewards::deposit_stake::( - &vault_id.collateral_currency(), - &vault_id.account_id, - amount.clone(), - )?; - Ok(()) } @@ -952,11 +940,6 @@ impl Pallet { // Withdraw `amount` of stake from the pool ext::staking::withdraw_stake::(vault_id, &vault_id.account_id, &amount.clone())?; - ext::pooled_rewards::withdraw_stake::( - &vault_id.collateral_currency(), - &vault_id.account_id, - amount.clone(), - )?; Ok(()) } @@ -1532,11 +1515,6 @@ impl Pallet { &to_be_released, )?; - ext::pooled_rewards::deposit_stake::( - &old_vault_id.collateral_currency(), - &old_vault_id.account_id, - to_be_released, - )?; } old_vault.execute_redeem_tokens(tokens)?; @@ -2198,7 +2176,7 @@ impl Pallet { let rich_vault: RichVault = vault.clone().into(); let rewarding_tokens = rich_vault.issued_tokens() - rich_vault.to_be_redeemed_tokens(); - assert_eq!(ext::reward::get_stake::(&vault_id).unwrap(), rewarding_tokens.amount()); + assert_eq!(ext::pooled_rewards::get_stake::(&vault_id).unwrap(), rewarding_tokens.amount()); } } diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index 6506ca63f..a45f4e1d0 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -48,8 +48,7 @@ frame_support::construct_runtime!( Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, - Rewards: reward::{Pallet, Call, Storage, Event}, - PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, @@ -176,14 +175,6 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } -impl reward::Config for Test { - type RuntimeEvent = TestEvent; - type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; -} - parameter_types! { pub const MinimumPeriod: Moment = 5; } @@ -240,11 +231,16 @@ impl currency::Config for Test { type AmountCompatibility = primitives::StellarCompatibility; } + +const BASE_CURRENCY_ID: CurrencyId = CurrencyId::XCM(2); parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = BASE_CURRENCY_ID; } + + impl fee::Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -254,6 +250,7 @@ impl fee::Config for Test { type UnsignedInner = Balance; type VaultRewards = Rewards; type VaultStaking = Staking; + type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } @@ -262,12 +259,13 @@ parameter_types! { pub const MaxRewardCurrencies: u32= 10; } + impl pooled_rewards::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; type PoolRewardsCurrencyId = CurrencyId; - type StakeId = AccountId; + type StakeId = VaultId; type MaxRewardCurrencies = MaxRewardCurrencies; } @@ -280,7 +278,6 @@ impl Config for Test { type RuntimeEvent = TestEvent; type Balance = Balance; type WeightInfo = vault_registry::SubstrateWeight; - type PoolRewards = PooledRewards; type GetGriefingCollateralCurrencyId = GetNativeCurrencyId; } diff --git a/pallets/vault-registry/src/types.rs b/pallets/vault-registry/src/types.rs index 806f81b1b..000a5edb6 100644 --- a/pallets/vault-registry/src/types.rs +++ b/pallets/vault-registry/src/types.rs @@ -352,7 +352,7 @@ impl RichVault { Ok(()) } else { let stake = self.freely_redeemable_tokens()?; - ext::reward::set_stake(&self.id(), &stake) + ext::pooled_rewards::set_stake(&self.id(), &stake) } } @@ -547,12 +547,6 @@ impl RichVault { let collateral = self.get_vault_collateral()?.min(amount)?; ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &collateral.clone())?; - ext::pooled_rewards::withdraw_stake::( - &vault_id.collateral_currency(), - &vault_id.account_id, - collateral.clone(), - )?; - self.increase_liquidated_collateral(&collateral)?; Ok(()) } @@ -570,12 +564,6 @@ impl RichVault { // "slash" vault first ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &to_withdraw.clone())?; - ext::pooled_rewards::withdraw_stake::( - &vault_id.collateral_currency(), - &vault_id.account_id, - to_withdraw, - )?; - // take remainder from nominators if let Some(to_slash) = to_slash { ext::staking::slash_stake::(&vault_id, &to_slash)?; @@ -636,7 +624,7 @@ impl RichVault { // todo: clear replace collateral? // withdraw stake from the reward pool - ext::reward::set_stake::(&vault_id, &Amount::zero(vault_id.wrapped_currency()))?; + ext::pooled_rewards::set_stake::(&vault_id, &Amount::zero(vault_id.wrapped_currency()))?; // Update vault: clear to_be_issued & issued_tokens, but don't touch to_be_redeemed let _ = self.update(|v| { diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index b4d93dbe9..4b20aed22 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -302,13 +302,6 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } -impl reward::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SignedFixedPoint = SignedFixedPoint; - type RewardId = VaultId; - type CurrencyId = CurrencyId; - type GetNativeCurrencyId = GetNativeCurrencyId; -} impl security::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -538,8 +531,11 @@ impl replace::Config for Runtime { type WeightInfo = replace::SubstrateWeight; } + +const BASE_CURRENCY_ID: CurrencyId = CurrencyId::XCM(2); parameter_types! { pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); + pub const GetBaseCurrency: CurrencyId = BASE_CURRENCY_ID; } impl fee::Config for Runtime { @@ -551,6 +547,7 @@ impl fee::Config for Runtime { type UnsignedInner = UnsignedInner; type VaultRewards = VaultRewards; type VaultStaking = VaultStaking; + type BaseCurrency = GetBaseCurrency; type OnSweep = currency::SweepFunds; type MaxExpectedValue = MaxExpectedValue; } @@ -558,7 +555,6 @@ impl fee::Config for Runtime { impl nomination::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = nomination::SubstrateWeight; - type PoolRewards = PooledRewards; } impl clients_info::Config for Runtime { @@ -577,7 +573,7 @@ impl pooled_rewards::Config for Runtime { type SignedFixedPoint = SignedFixedPoint; type PoolId = CurrencyId; type PoolRewardsCurrencyId = CurrencyId; - type StakeId = AccountId; + type StakeId = VaultId; type MaxRewardCurrencies = MaxRewardCurrencies; } @@ -599,7 +595,7 @@ construct_runtime! { StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event} = 10, - VaultRewards: reward::{Pallet, Storage, Event} = 15, + VaultRewards: pooled_rewards::{Pallet, Storage, Event} = 15, VaultStaking: staking::{Pallet, Storage, Event} = 16, Currency: currency::{Pallet} = 17, @@ -614,7 +610,6 @@ construct_runtime! { Nomination: nomination::{Pallet, Call, Config, Storage, Event} = 28, DiaOracleModule: dia_oracle::{Pallet, Call, Config, Storage, Event} = 29, ClientsInfo: clients_info::{Pallet, Call, Storage, Event} = 30, - PooledRewards: pooled_rewards::{Pallet, Call, Storage, Event} = 31, } } From 9b551cef8e12ef345e524de7e028351688e8be41 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 12 Oct 2023 12:27:27 -0300 Subject: [PATCH 11/52] set reward staking to be equal to total stake (pallet stake) --- pallets/fee/src/lib.rs | 34 +++++++++++---------- pallets/nomination/src/ext.rs | 47 +++++++++++++++-------------- pallets/nomination/src/lib.rs | 15 ++++++++- pallets/vault-registry/src/ext.rs | 30 +++++------------- pallets/vault-registry/src/lib.rs | 21 +++++++++++-- pallets/vault-registry/src/types.rs | 21 ++++++------- 6 files changed, 92 insertions(+), 76 deletions(-) diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index 7ff5b56d8..7cebc903b 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -28,7 +28,6 @@ pub use pallet::*; pub use pooled_rewards::RewardsApi; use types::{BalanceOf, DefaultVaultId, SignedFixedPoint, UnsignedFixedPoint}; - #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -58,7 +57,6 @@ pub mod pallet { UnsignedFixedPoint = UnsignedFixedPoint, SignedFixedPoint = SignedFixedPoint, > - { /// The fee module id, used for deriving its sovereign account ID. #[pallet::constant] @@ -490,34 +488,37 @@ impl Pallet { } fn distribute(reward: &Amount) -> Result, DispatchError> { - //fetch total stake (all), and calulate total usd stake across pools //distribute the rewards into each reward pool for each collateral, //taking into account it's value in usd - let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; - let total_stake_in_usd= BalanceOf::::default(); - for (currency_id, stake) in total_stakes.clone().into_iter(){ + //TODO this logic will go to the reward-distribution pallet most likely - let stake_in_amount = Amount::::new(stake,currency_id); + let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; + let total_stake_in_usd = BalanceOf::::default(); + for (currency_id, stake) in total_stakes.clone().into_iter() { + let stake_in_amount = Amount::::new(stake, currency_id); let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - total_stake_in_usd.checked_add(&stake_in_usd.amount()).ok_or(Error::::Overflow)?; + total_stake_in_usd + .checked_add(&stake_in_usd.amount()) + .ok_or(Error::::Overflow)?; } let error_reward_accum = Amount::::zero(reward.currency()); - for (currency_id, stake) in total_stakes.into_iter(){ - - let stake_in_amount = Amount::::new(stake,currency_id); + for (currency_id, stake) in total_stakes.into_iter() { + let stake_in_amount = Amount::::new(stake, currency_id); let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - let percentage = Perquintill::from_rational(stake_in_usd.amount(),total_stake_in_usd); - + let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); + //TODO multiply with floor or ceil? let reward_for_pool = percentage.mul_floor(reward.amount()); - if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(),reward_for_pool).is_err() { + if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) + .is_err() + { error_reward_accum.checked_add(&reward)?; - } + } } Ok(error_reward_accum) } @@ -529,7 +530,8 @@ impl Pallet { vault_id: &DefaultVaultId, ) -> DispatchResult { for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { - let reward = Rewards::withdraw_reward(&vault_id.collateral_currency(), &vault_id, currency_id)?; + let reward = + Rewards::withdraw_reward(&vault_id.collateral_currency(), &vault_id, currency_id)?; Staking::distribute_reward(vault_id, reward, currency_id)?; } diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index 5dd811205..1f725dfc7 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -77,6 +77,7 @@ pub(crate) mod fee { #[cfg_attr(test, mockable)] pub(crate) mod staking { use crate::BalanceOf; + use currency::Amount; use frame_support::dispatch::DispatchError; use staking::Staking; use vault_registry::DefaultVaultId; @@ -114,28 +115,28 @@ pub(crate) mod staking { ) -> Result, DispatchError> { T::VaultStaking::force_refund(vault_id) } + + pub fn total_current_stake_as_amount( + vault_id: &DefaultVaultId, + ) -> Result, DispatchError> { + let vault_total_stake = T::VaultStaking::total_stake(vault_id)?; + Ok(Amount::::new(vault_total_stake, vault_id.collateral_currency())) + } } -// #[cfg_attr(test, mockable)] -// pub(crate) mod pooled_rewards { - -// use currency::{Amount, CurrencyId}; -// use frame_support::dispatch::DispatchResult; -// use pooled_rewards::RewardsApi; - -// pub fn deposit_stake( -// currency_id: &CurrencyId, -// account_id: &::AccountId, -// amount: Amount, -// ) -> DispatchResult { -// T::PoolRewards::deposit_stake(currency_id, account_id, amount.amount()) -// } - -// pub fn withdraw_stake( -// currency_id: &CurrencyId, -// account_id: &::AccountId, -// amount: Amount, -// ) -> DispatchResult { -// T::PoolRewards::withdraw_stake(currency_id, account_id, amount.amount()) -// } -// } +#[cfg_attr(test, mockable)] +pub(crate) mod pooled_rewards { + + use currency::Amount; + use frame_support::dispatch::DispatchError; + use pooled_rewards::RewardsApi; + + use crate::DefaultVaultId; + + pub fn set_stake( + vault_id: &DefaultVaultId, + amount: &Amount, + ) -> Result<(), DispatchError> { + T::VaultRewards::set_stake(&vault_id.collateral_currency(), &vault_id, amount.amount()) + } +} diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index a993bbe32..4f9c97b01 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -56,7 +56,6 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - } #[pallet::event] @@ -250,6 +249,10 @@ impl Pallet { amount.unlock_on(&vault_id.account_id)?; amount.transfer(&vault_id.account_id, nominator_id)?; + //decrease the stake in the reward pallet based on this updated stake + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + Self::deposit_event(Event::::WithdrawCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), @@ -289,6 +292,12 @@ impl Pallet { amount.lock_on(&vault_id.account_id)?; ext::vault_registry::try_increase_total_backing_collateral(&vault_id.currencies, &amount)?; + //increase the stake in the reward pallet based on this updated stake + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + //TODO Perhaps we should cap it to "some" amount, to prevent + //extreme overcollateralization to just get rewards + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + Self::deposit_event(Event::::DepositCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), @@ -331,6 +340,10 @@ impl Pallet { &refunded_collateral, )?; + //update the reward pool + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + >::remove(vault_id); Self::deposit_event(Event::::NominationOptOut { vault_id: vault_id.clone() }); Ok(()) diff --git a/pallets/vault-registry/src/ext.rs b/pallets/vault-registry/src/ext.rs index 1b3a970e1..ccee6b638 100644 --- a/pallets/vault-registry/src/ext.rs +++ b/pallets/vault-registry/src/ext.rs @@ -73,8 +73,14 @@ pub(crate) mod staking { ) -> Result, DispatchError> { T::VaultStaking::total_stake(vault_id) } -} + pub fn total_current_stake_as_amount( + vault_id: &DefaultVaultId, + ) -> Result, DispatchError> { + let vault_total_stake = T::VaultStaking::total_stake(vault_id)?; + Ok(Amount::::new(vault_total_stake, vault_id.collateral_currency())) + } +} #[cfg_attr(test, mockable)] pub(crate) mod pooled_rewards { @@ -82,34 +88,14 @@ pub(crate) mod pooled_rewards { use currency::Amount; use frame_support::dispatch::DispatchError; use pooled_rewards::RewardsApi; - use sp_core::Get; use crate::DefaultVaultId; - // pub fn deposit_stake( - // currency_id: &CurrencyId, - // vault_id: &DefaultVaultId, - // amount: Amount, - // ) -> DispatchResult { - // T::VaultRewards::deposit_stake(currency_id, vault_id, amount.amount()) - // } - - // pub fn withdraw_stake( - // currency_id: &CurrencyId, - // vault_id: &DefaultVaultId, - // amount: Amount, - // ) -> DispatchResult { - // T::VaultRewards::withdraw_stake(currency_id, vault_id, amount.amount()) - // } - pub fn set_stake( vault_id: &DefaultVaultId, amount: &Amount, ) -> Result<(), DispatchError> { - //we need to normalize the amount into base currency, to handle vaults with same collateral - //but different issue tokens. - let stake = amount.convert_to(::BaseCurrency::get())?; - T::VaultRewards::set_stake(&vault_id.collateral_currency(), &vault_id, stake.amount()) + T::VaultRewards::set_stake(&vault_id.collateral_currency(), &vault_id, amount.amount()) } #[cfg(feature = "integration-tests")] diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 491a75681..3d43723da 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -108,7 +108,6 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - /// Currency used for griefing collateral, e.g. DOT. #[pallet::constant] type GetGriefingCollateralCurrencyId: Get>; @@ -922,6 +921,9 @@ impl Pallet { // Deposit `amount` of stake in the pool ext::staking::deposit_stake::(vault_id, &vault_id.account_id, &amount.clone())?; + //update staking reward + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; Ok(()) } @@ -941,6 +943,10 @@ impl Pallet { // Withdraw `amount` of stake from the pool ext::staking::withdraw_stake::(vault_id, &vault_id.account_id, &amount.clone())?; + //update staking reward + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + Ok(()) } @@ -1017,6 +1023,10 @@ impl Pallet { amount.unlock_on(&vault_id.account_id)?; Self::decrease_total_backing_collateral(&vault_id.currencies, amount)?; ext::staking::slash_stake::(vault_id, amount)?; + + //update staking reward + let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; Ok(()) } @@ -1508,6 +1518,7 @@ impl Pallet { )?; old_vault.decrease_liquidated_collateral(&to_be_released)?; + //TODO why old_vault_id? // deposit old-vault's collateral (this was withdrawn on liquidation) ext::staking::deposit_stake::( old_vault_id, @@ -1515,6 +1526,9 @@ impl Pallet { &to_be_released, )?; + //update staking reward of new + let new_total_stake = ext::staking::total_current_stake_as_amount::(new_vault_id)?; + ext::pooled_rewards::set_stake::(&new_vault_id, &new_total_stake)?; } old_vault.execute_redeem_tokens(tokens)?; @@ -2176,7 +2190,10 @@ impl Pallet { let rich_vault: RichVault = vault.clone().into(); let rewarding_tokens = rich_vault.issued_tokens() - rich_vault.to_be_redeemed_tokens(); - assert_eq!(ext::pooled_rewards::get_stake::(&vault_id).unwrap(), rewarding_tokens.amount()); + assert_eq!( + ext::pooled_rewards::get_stake::(&vault_id).unwrap(), + rewarding_tokens.amount() + ); } } diff --git a/pallets/vault-registry/src/types.rs b/pallets/vault-registry/src/types.rs index 000a5edb6..6b4bfc358 100644 --- a/pallets/vault-registry/src/types.rs +++ b/pallets/vault-registry/src/types.rs @@ -328,17 +328,17 @@ impl RichVault { pub(crate) fn execute_issue_tokens(&mut self, tokens: &Amount) -> DispatchResult { self.decrease_to_be_issued(tokens)?; self.increase_issued(tokens)?; - self.update_stake() + Ok(()) } pub(crate) fn request_redeem_tokens(&mut self, tokens: &Amount) -> DispatchResult { self.increase_to_be_redeemed(tokens)?; - self.update_stake() + Ok(()) } pub(crate) fn cancel_redeem_tokens(&mut self, tokens: &Amount) -> DispatchResult { self.decrease_to_be_redeemed(tokens)?; - self.update_stake() + Ok(()) } pub(crate) fn execute_redeem_tokens(&mut self, tokens: &Amount) -> DispatchResult { @@ -347,15 +347,6 @@ impl RichVault { self.decrease_issued(tokens) } - fn update_stake(&self) -> DispatchResult { - if self.data.is_liquidated() { - Ok(()) - } else { - let stake = self.freely_redeemable_tokens()?; - ext::pooled_rewards::set_stake(&self.id(), &stake) - } - } - pub(crate) fn wrapped_currency(&self) -> CurrencyId { self.data.id.wrapped_currency() } @@ -547,6 +538,9 @@ impl RichVault { let collateral = self.get_vault_collateral()?.min(amount)?; ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &collateral.clone())?; + let new_total_stake = ext::staking::total_current_stake_as_amount::(&vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + self.increase_liquidated_collateral(&collateral)?; Ok(()) } @@ -569,6 +563,9 @@ impl RichVault { ext::staking::slash_stake::(&vault_id, &to_slash)?; } + let new_total_stake = ext::staking::total_current_stake_as_amount::(&vault_id)?; + ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + Pallet::::transfer_funds( CurrencySource::LiquidatedCollateral(self.id()), CurrencySource::LiquidationVault(vault_id.currencies), From 30741b69f955f8b193838b9160af89e6c6ecb6db Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 12 Oct 2023 12:53:39 -0300 Subject: [PATCH 12/52] remove basecurrency from fee, conversion --- pallets/currency/src/lib.rs | 2 +- pallets/fee/src/lib.rs | 68 +++++++++++++++--------------- pallets/fee/src/mock.rs | 6 --- pallets/issue/src/mock.rs | 5 --- pallets/nomination/src/mock.rs | 7 --- pallets/oracle/src/lib.rs | 10 ++++- pallets/pooled-rewards/src/lib.rs | 45 ++++++++------------ pallets/redeem/src/mock.rs | 4 -- pallets/replace/src/mock.rs | 6 --- pallets/vault-registry/src/mock.rs | 7 --- testchain/runtime/src/lib.rs | 5 --- 11 files changed, 62 insertions(+), 103 deletions(-) diff --git a/pallets/currency/src/lib.rs b/pallets/currency/src/lib.rs index 58d366c2d..f836b0e29 100644 --- a/pallets/currency/src/lib.rs +++ b/pallets/currency/src/lib.rs @@ -89,7 +89,7 @@ pub mod pallet { + Copy + Default + Debug - +From; + + From; /// Relay chain currency e.g. DOT/KSM #[pallet::constant] diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index 7cebc903b..a5bdb438b 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -15,7 +15,7 @@ use frame_support::{ }; #[cfg(test)] use mocktopus::macros::mockable; -use sp_arithmetic::{traits::*, FixedPointNumber, FixedPointOperand, Perquintill}; +use sp_arithmetic::{traits::*, FixedPointNumber, FixedPointOperand}; use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned}; use sp_std::{ convert::{TryFrom, TryInto}, @@ -120,9 +120,6 @@ pub mod pallet { /// Handler to transfer undistributed rewards. type OnSweep: OnSweep>; - //currency to usd interface - type BaseCurrency: Get>; - /// Maximum expected value to set the storage fields to. #[pallet::constant] type MaxExpectedValue: Get>; @@ -488,39 +485,44 @@ impl Pallet { } fn distribute(reward: &Amount) -> Result, DispatchError> { + //TODO commented logic will go to the reward-distribution pallet most likely + //fetch total stake (all), and calulate total usd stake across pools //distribute the rewards into each reward pool for each collateral, //taking into account it's value in usd - //TODO this logic will go to the reward-distribution pallet most likely - - let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; - let total_stake_in_usd = BalanceOf::::default(); - for (currency_id, stake) in total_stakes.clone().into_iter() { - let stake_in_amount = Amount::::new(stake, currency_id); - let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - total_stake_in_usd - .checked_add(&stake_in_usd.amount()) - .ok_or(Error::::Overflow)?; - } - - let error_reward_accum = Amount::::zero(reward.currency()); - - for (currency_id, stake) in total_stakes.into_iter() { - let stake_in_amount = Amount::::new(stake, currency_id); - let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); - - //TODO multiply with floor or ceil? - let reward_for_pool = percentage.mul_floor(reward.amount()); - - if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) - .is_err() - { - error_reward_accum.checked_add(&reward)?; - } - } - Ok(error_reward_accum) + // let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; + // let total_stake_in_usd = BalanceOf::::default(); + // for (currency_id, stake) in total_stakes.clone().into_iter() { + // let stake_in_amount = Amount::::new(stake, currency_id); + // let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; + // total_stake_in_usd + // .checked_add(&stake_in_usd.amount()) + // .ok_or(Error::::Overflow)?; + // } + + // let error_reward_accum = Amount::::zero(reward.currency()); + + // for (currency_id, stake) in total_stakes.into_iter() { + // let stake_in_amount = Amount::::new(stake, currency_id); + // let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; + // let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); + + // // multiply with floor or ceil? + // let reward_for_pool = percentage.mul_floor(reward.amount()); + // let error_reward_accum = Amount::::zero(reward.currency()); + // if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) + // .is_err() + // { + // error_reward_accum.checked_add(&reward)?; + // } + // } + + //TODO INTERFACE REQUIRED + //From reward-distribution pallet we will pass the reward in wrapped token and + //it will be in charge of distributing to all pools by it's stake in usd + + Ok(Amount::::zero(reward.currency())) } pub fn distribute_from_reward_pool< diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index 2ac0e957b..7b665a715 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -221,16 +221,11 @@ impl currency::Config for Test { type AmountCompatibility = primitives::StellarCompatibility; } - -const USD: CurrencyId = CurrencyId::XCM(2); - parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = USD; } - impl Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -242,7 +237,6 @@ impl Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type BaseCurrency = GetBaseCurrency; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 26a44dc97..fa92c96db 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -286,13 +286,9 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } - -const USD: CurrencyId = CurrencyId::XCM(2); - parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -304,7 +300,6 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; - type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index fda1ee6ab..15f4dc013 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -177,8 +177,6 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } - - parameter_types! { pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg"); } @@ -252,15 +250,11 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } -const USD: CurrencyId = CurrencyId::XCM(2); - parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = USD; } - impl fee::Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -272,7 +266,6 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type BaseCurrency = GetBaseCurrency; } impl oracle::Config for Test { diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index ef863161f..fc6164411 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -356,6 +356,14 @@ impl Pallet { } } +// pub trait CurrencyToUsd +// { +// pub fn currency_to_usd( +// amount: BalanceOf, +// currency_id: CurrencyId, +// )-> Result, DispatchError>; + +// } // pub trait CurrencyToUsd // { @@ -364,4 +372,4 @@ impl Pallet { // currency_id: CurrencyId, // )-> Result, DispatchError>; -// } \ No newline at end of file +// } diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs index c5ba4f9f0..5bf4f367e 100644 --- a/pallets/pooled-rewards/src/lib.rs +++ b/pallets/pooled-rewards/src/lib.rs @@ -379,8 +379,6 @@ impl, I: 'static> Pallet { }); Ok(reward) } - - } pub trait RewardsApi @@ -446,12 +444,11 @@ where } //get total stake for each `pool_id` in the pallet - fn get_total_stake_all_pools()-> Result, DispatchError>; - - + fn get_total_stake_all_pools() -> Result, DispatchError>; } -impl RewardsApi for Pallet +impl RewardsApi + for Pallet where T: Config, I: 'static, @@ -466,7 +463,7 @@ where fn distribute_reward( pool_id: &T::PoolId, - currency_id:T::PoolRewardsCurrencyId, + currency_id: T::PoolRewardsCurrencyId, amount: Balance, ) -> DispatchResult { Pallet::::distribute_reward( @@ -536,18 +533,18 @@ where ) } - fn get_total_stake_all_pools()-> Result, DispatchError>{ - let mut pool_vec: Vec<(T::PoolId,Balance)> = Vec::new(); - for (pool_id, pool_total_stake) in TotalStake::::iter(){ + fn get_total_stake_all_pools() -> Result, DispatchError> { + let mut pool_vec: Vec<(T::PoolId, Balance)> = Vec::new(); + for (pool_id, pool_total_stake) in TotalStake::::iter() { + let pool_stake_as_balance: Balance = pool_total_stake + .truncate_to_inner() + .ok_or(Error::::TryIntoIntError)? + .try_into() + .map_err(|_| Error::::TryIntoIntError)?; - let pool_stake_as_balance: Balance = pool_total_stake.truncate_to_inner() - .ok_or(Error::::TryIntoIntError)? - .try_into() - .map_err(|_| Error::::TryIntoIntError)?; - - pool_vec.push((pool_id,pool_stake_as_balance)); + pool_vec.push((pool_id, pool_stake_as_balance)); } - return Ok(pool_vec); + return Ok(pool_vec) } } @@ -565,19 +562,11 @@ where Ok(()) } - fn compute_reward( - _: &PoolId, - _: &StakeId, - _: CurrencyId, - ) -> Result { + fn compute_reward(_: &PoolId, _: &StakeId, _: CurrencyId) -> Result { Ok(Default::default()) } - fn withdraw_reward( - _: &PoolId, - _: &StakeId, - _: CurrencyId, - ) -> Result { + fn withdraw_reward(_: &PoolId, _: &StakeId, _: CurrencyId) -> Result { Ok(Default::default()) } @@ -597,7 +586,7 @@ where Ok(()) } - fn get_total_stake_all_pools()-> Result, DispatchError>{ + fn get_total_stake_all_pools() -> Result, DispatchError> { Ok(Default::default()) } } diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index a50f6ba5a..977e49f90 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -317,12 +317,9 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } -const USD: CurrencyId = CurrencyId::XCM(2); - parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -334,7 +331,6 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; - type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index 89b5f046d..880462135 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -175,8 +175,6 @@ impl orml_tokens::Config for Test { type DustRemovalWhitelist = Everything; } - - parameter_types! { pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg"); } @@ -316,12 +314,9 @@ impl oracle::Config for Test { type DataFeedProvider = DataCollector; } -const USD: CurrencyId = CurrencyId::XCM(2); - parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = USD; } impl fee::Config for Test { @@ -333,7 +328,6 @@ impl fee::Config for Test { type UnsignedInner = UnsignedInner; type VaultRewards = Rewards; type VaultStaking = Staking; - type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index a45f4e1d0..20d2dc167 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -231,16 +231,11 @@ impl currency::Config for Test { type AmountCompatibility = primitives::StellarCompatibility; } - -const BASE_CURRENCY_ID: CurrencyId = CurrencyId::XCM(2); parameter_types! { pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = BASE_CURRENCY_ID; } - - impl fee::Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -250,7 +245,6 @@ impl fee::Config for Test { type UnsignedInner = Balance; type VaultRewards = Rewards; type VaultStaking = Staking; - type BaseCurrency = GetBaseCurrency; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; } @@ -259,7 +253,6 @@ parameter_types! { pub const MaxRewardCurrencies: u32= 10; } - impl pooled_rewards::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index 4b20aed22..23543738c 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -302,7 +302,6 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } - impl security::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = security::SubstrateWeight; @@ -531,11 +530,8 @@ impl replace::Config for Runtime { type WeightInfo = replace::SubstrateWeight; } - -const BASE_CURRENCY_ID: CurrencyId = CurrencyId::XCM(2); parameter_types! { pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); - pub const GetBaseCurrency: CurrencyId = BASE_CURRENCY_ID; } impl fee::Config for Runtime { @@ -547,7 +543,6 @@ impl fee::Config for Runtime { type UnsignedInner = UnsignedInner; type VaultRewards = VaultRewards; type VaultStaking = VaultStaking; - type BaseCurrency = GetBaseCurrency; type OnSweep = currency::SweepFunds; type MaxExpectedValue = MaxExpectedValue; } From f5680714084bca98b1e437106af4e18a233d75d0 Mon Sep 17 00:00:00 2001 From: gianfra-t <96739519+gianfra-t@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:57:11 -0300 Subject: [PATCH 13/52] remove comments --- pallets/oracle/src/lib.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index fc6164411..1ae246b6e 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -356,20 +356,3 @@ impl Pallet { } } -// pub trait CurrencyToUsd -// { -// pub fn currency_to_usd( -// amount: BalanceOf, -// currency_id: CurrencyId, -// )-> Result, DispatchError>; - -// } - -// pub trait CurrencyToUsd -// { -// pub fn currency_to_usd( -// amount: BalanceOf, -// currency_id: CurrencyId, -// )-> Result, DispatchError>; - -// } From ab0fa4f5a5b94f3bc9ac5d736571d73a011f0333 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 12 Oct 2023 14:13:46 -0300 Subject: [PATCH 14/52] fix incorrect vault_id update --- pallets/vault-registry/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 3d43723da..5ef61c429 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -1527,8 +1527,8 @@ impl Pallet { )?; //update staking reward of new - let new_total_stake = ext::staking::total_current_stake_as_amount::(new_vault_id)?; - ext::pooled_rewards::set_stake::(&new_vault_id, &new_total_stake)?; + let new_total_stake = ext::staking::total_current_stake_as_amount::(old_vault_id)?; + ext::pooled_rewards::set_stake::(&old_vault_id, &new_total_stake)?; } old_vault.execute_redeem_tokens(tokens)?; From c126287c0ab19540e951960183b6a883f1616bb5 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 12 Oct 2023 15:45:44 -0300 Subject: [PATCH 15/52] comments, fmt --- pallets/vault-registry/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 5ef61c429..b5877e6c7 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -1518,7 +1518,6 @@ impl Pallet { )?; old_vault.decrease_liquidated_collateral(&to_be_released)?; - //TODO why old_vault_id? // deposit old-vault's collateral (this was withdrawn on liquidation) ext::staking::deposit_stake::( old_vault_id, From ef50a31bee18c74d14da08cf1f424150e7a7bea4 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 12 Oct 2023 18:37:05 -0300 Subject: [PATCH 16/52] WIP reward-distribution hook and percentage caculation --- Cargo.lock | 3 + pallets/fee/src/lib.rs | 33 ----- pallets/oracle/src/lib.rs | 3 + pallets/reward-distribution/Cargo.toml | 4 + .../reward-distribution/src/benchmarking.rs | 5 + .../src/default_weights.rs | 20 +-- pallets/reward-distribution/src/ext.rs | 19 +++ pallets/reward-distribution/src/lib.rs | 139 +++++++++++++++++- pallets/reward-distribution/src/types.rs | 7 +- 9 files changed, 182 insertions(+), 51 deletions(-) create mode 100644 pallets/reward-distribution/src/ext.rs diff --git a/Cargo.lock b/Cargo.lock index 9332f7537..8342cb0e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6865,11 +6865,13 @@ dependencies = [ name = "reward-distribution" version = "0.1.0" dependencies = [ + "currency", "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", "parity-scale-codec", + "pooled-rewards", "scale-info", "security", "serde", @@ -6878,6 +6880,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "spacewalk-primitives", ] [[package]] diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index a5bdb438b..3f8ce4a21 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -485,39 +485,6 @@ impl Pallet { } fn distribute(reward: &Amount) -> Result, DispatchError> { - //TODO commented logic will go to the reward-distribution pallet most likely - - //fetch total stake (all), and calulate total usd stake across pools - //distribute the rewards into each reward pool for each collateral, - //taking into account it's value in usd - - // let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; - // let total_stake_in_usd = BalanceOf::::default(); - // for (currency_id, stake) in total_stakes.clone().into_iter() { - // let stake_in_amount = Amount::::new(stake, currency_id); - // let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - // total_stake_in_usd - // .checked_add(&stake_in_usd.amount()) - // .ok_or(Error::::Overflow)?; - // } - - // let error_reward_accum = Amount::::zero(reward.currency()); - - // for (currency_id, stake) in total_stakes.into_iter() { - // let stake_in_amount = Amount::::new(stake, currency_id); - // let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - // let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); - - // // multiply with floor or ceil? - // let reward_for_pool = percentage.mul_floor(reward.amount()); - // let error_reward_accum = Amount::::zero(reward.currency()); - // if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) - // .is_err() - // { - // error_reward_accum.checked_add(&reward)?; - // } - // } - //TODO INTERFACE REQUIRED //From reward-distribution pallet we will pass the reward in wrapped token and //it will be in charge of distributing to all pools by it's stake in usd diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index 5fa5834fa..1ead175ec 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -368,3 +368,6 @@ impl Pallet { } } +pub trait OracleApi { + fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result; +} diff --git a/pallets/reward-distribution/Cargo.toml b/pallets/reward-distribution/Cargo.toml index 5523502a4..0c9f91320 100644 --- a/pallets/reward-distribution/Cargo.toml +++ b/pallets/reward-distribution/Cargo.toml @@ -16,13 +16,17 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } + frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false, optional = true } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } security = { path = "../security", default-features = false } +currency = { path = "../currency", default-features = false } +pooled-rewards = { path = "../pooled-rewards", default-features = false } [features] default = ["std"] diff --git a/pallets/reward-distribution/src/benchmarking.rs b/pallets/reward-distribution/src/benchmarking.rs index 0658740da..af4879aa8 100644 --- a/pallets/reward-distribution/src/benchmarking.rs +++ b/pallets/reward-distribution/src/benchmarking.rs @@ -20,6 +20,11 @@ pub mod benchmarks { assert_eq!(RewardDistribution::::reward_per_block(), Some(new_reward_per_block)); } + #[benchmark] + fn on_initialize() { + Timestamp::::set_timestamp(1000u32.into()); + } + impl_benchmark_test_suite!( RewardDistribution, crate::mock::ExtBuilder::build(), diff --git a/pallets/reward-distribution/src/default_weights.rs b/pallets/reward-distribution/src/default_weights.rs index 9947ff0c5..c0213b4d2 100644 --- a/pallets/reward-distribution/src/default_weights.rs +++ b/pallets/reward-distribution/src/default_weights.rs @@ -31,6 +31,7 @@ use core::marker::PhantomData; /// Weight functions needed for reward_distribution. pub trait WeightInfo { fn set_reward_per_block() -> Weight; + fn on_initialize() -> Weight; } /// Weights for reward_distribution using the Substrate node and recommended hardware. @@ -48,20 +49,13 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(4_000_000, 0) .saturating_add(T::DbWeight::get().writes(2_u64)) } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - /// Storage: RewardDistribution RewardsAdaptedAt (r:0 w:1) - /// Proof: RewardDistribution RewardsAdaptedAt (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: RewardDistribution RewardPerBlock (r:0 w:1) - /// Proof: RewardDistribution RewardPerBlock (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - fn set_reward_per_block() -> Weight { + fn on_initialize() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(2_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) } -} \ No newline at end of file +} diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs new file mode 100644 index 000000000..b35ffd5b1 --- /dev/null +++ b/pallets/reward-distribution/src/ext.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +use mocktopus::macros::mockable; + +#[cfg_attr(test, mockable)] +pub(crate) mod security { + use frame_support::dispatch::DispatchResult; + use sp_runtime::DispatchError; + + pub fn ensure_parachain_status_running() -> DispatchResult { + >::ensure_parachain_status_running() + } + + pub fn parachain_block_expired( + opentime: T::BlockNumber, + period: T::BlockNumber, + ) -> Result { + >::parachain_block_expired(opentime, period) + } +} diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index bdcdb13ae..b3fee1ec4 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -9,17 +9,26 @@ mod default_weights; pub use default_weights::{SubstrateWeight, WeightInfo}; -use crate::types::{AccountIdOf, BalanceOf}; +use crate::types::{AccountIdOf, BalanceOf, DefaultVaultId}; +use codec::{FullCodec, MaxEncodedLen}; use frame_support::{ dispatch::DispatchResult, + pallet_prelude::DispatchError, traits::{Currency, Get}, transactional, }; +use pooled_rewards::RewardsApi; +use primitives::CurrencyId; use sp_arithmetic::Perquintill; - +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, CheckedAdd, One}, + FixedPointOperand, +}; +use sp_std::fmt::Debug; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod ext; #[cfg(test)] mod mock; @@ -51,7 +60,31 @@ pub mod pallet { type WeightInfo: WeightInfo; /// The currency trait. - type Currency: Currency>; + type Currency: Currency> + Clone; + + //balance + type Balance: AtLeast32BitUnsigned + + TypeInfo + + FixedPointOperand + + MaybeSerializeDeserialize + + FullCodec + + MaxEncodedLen + + Copy + + Default + + Debug + + Clone + + From; + + type VaultRewards: pooled_rewards::RewardsApi< + Self::Currency, + DefaultVaultId, + BalanceOf, + Self::Currency, + >; + + type MaxCurrencies: Get; + + type OracleApi: ToUsdApi; /// Defines the interval (in number of blocks) at which the reward per block decays. #[pallet::constant] @@ -72,6 +105,15 @@ pub mod pallet { #[pallet::error] pub enum Error {} + #[pallet::hooks] + impl Hooks for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + Self::distribute_rewards(n); + //TODO benchmark this weight properly + ::WeightInfo::on_initialize() + } + } + /// Reward per block. #[pallet::storage] #[pallet::getter(fn reward_per_block)] @@ -81,6 +123,14 @@ pub mod pallet { #[pallet::getter(fn rewards_adapted_at)] pub(super) type RewardsAdaptedAt = StorageValue<_, BlockNumberFor, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn rewards_percentage)] + pub(super) type RewardsPercentage = StorageValue< + _, + BoundedVec<(CurrencyId, Perquintill), ::MaxCurrencies>, + OptionQuery, + >; + #[pallet::call] impl Pallet { /// Sets the reward per block. @@ -101,3 +151,86 @@ pub mod pallet { } } } + +impl Pallet { + pub fn distribute_rewards(height: T::BlockNumber) { + //get reward per block + let reward_per_block = RewardPerBlock::::get(); + if reward_per_block == None { + return + } + + if let Err(_) = ext::security::ensure_parachain_status_running::() { + return + } + + let mut reward_this_block = reward_per_block.expect("checked for none"); + //update the reward per block if decay interval passed + let rewards_adapted_at = RewardsAdaptedAt::::get(); + if ext::security::parachain_block_expired::( + rewards_adapted_at.expect("checked for none"), + T::DecayInterval::get() - T::BlockNumber::one(), + ) + .unwrap() + { + let decay_rate = T::DecayRate::get(); + let reward_this_block = + decay_rate.mul_floor(reward_per_block.expect("checked for none")); + RewardPerBlock::::set(Some(new_reward_per_block)); + + RewardsAdaptedAt::::set(Some(height)); + } + + let total_reward_stake = Self::caculate_reward_stake_percentages(reward_this_block).unwrap(); + + + } + + //fetch total stake (all), and calulate total usd stake in percentage across pools + fn caculate_reward_stake_percentages() -> Result<(), ()> { + let total_stakes = T::VaultRewards::get_total_stake_all_pools().unwrap(); + let total_stake_in_usd = BalanceOf::::default(); + for (currency_id, stake) in total_stakes.clone().into_iter() { + let stake_in_usd = T::OracleApi::currency_to_usd(stake, currency_id).unwrap(); + total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); + } + Ok(total_stake_in_usd) + + let percentages_vec: Vec::new(); + for (currency_id, stake) in total_stakes.into_iter() { + let stake_in_amount = Amount::::new(stake, currency_id); + let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; + let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); + + percentages_vec.push((currency_id, percentage)) + } + + let bounded_vec = BoundedVec::try_from(percentages_vec).expect("should not be longer than max currencies"); + + RewardsPercentage::::set(bounded_vec); + } + + pub fn distribute_rewards(reward)-> Result<(),(){ + // multiply with floor or ceil? + + let stake_percentages = RewardsPercentage::::get(); + + for (currency_id, stake) in stake_percentages.into_iter() { + let reward_for_pool = percentage.mul_floor(reward.amount()); + let error_reward_accum = Amount::::zero(reward.currency()); + if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) + .is_err() + { + error_reward_accum.checked_add(&reward)?; + } + } + + } + +} + + + +pub trait ToUsdApi { + fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result; +} diff --git a/pallets/reward-distribution/src/types.rs b/pallets/reward-distribution/src/types.rs index e887f80dc..44989dc5e 100644 --- a/pallets/reward-distribution/src/types.rs +++ b/pallets/reward-distribution/src/types.rs @@ -1,6 +1,9 @@ use crate::Config; -use frame_support::traits::Currency; +//use currency::CurrencyId; +use primitives::{CurrencyId, VaultId}; pub type AccountIdOf = ::AccountId; -pub(crate) type BalanceOf = <::Currency as Currency>>::Balance; +pub(crate) type BalanceOf = ::Balance; + +pub(crate) type DefaultVaultId = VaultId<::AccountId, CurrencyId>; From 496197f168e45b00e39911b28b3489906ddad6d1 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 13 Oct 2023 09:50:08 -0300 Subject: [PATCH 17/52] WIP base implementation --- pallets/reward-distribution/src/lib.rs | 107 ++++++++++++++----------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index b3fee1ec4..86e4c13fb 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -7,10 +7,9 @@ mod default_weights; -pub use default_weights::{SubstrateWeight, WeightInfo}; - use crate::types::{AccountIdOf, BalanceOf, DefaultVaultId}; -use codec::{FullCodec, MaxEncodedLen}; +use codec::{FullCodec, FullEncode, MaxEncodedLen}; +pub use default_weights::{SubstrateWeight, WeightInfo}; use frame_support::{ dispatch::DispatchResult, pallet_prelude::DispatchError, @@ -18,10 +17,10 @@ use frame_support::{ transactional, }; use pooled_rewards::RewardsApi; -use primitives::CurrencyId; use sp_arithmetic::Perquintill; +use sp_core::bounded_vec::BoundedVec; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedAdd, One}, + traits::{AtLeast32BitUnsigned, CheckedAdd, One, Zero}, FixedPointOperand, }; use sp_std::fmt::Debug; @@ -60,7 +59,13 @@ pub mod pallet { type WeightInfo: WeightInfo; /// The currency trait. - type Currency: Currency> + Clone; + type Currency: Currency> + + Clone + + Decode + + FullEncode + + TypeInfo + + MaxEncodedLen + + Debug; //balance type Balance: AtLeast32BitUnsigned @@ -86,6 +91,8 @@ pub mod pallet { type OracleApi: ToUsdApi; + type NativeToken: Get; + /// Defines the interval (in number of blocks) at which the reward per block decays. #[pallet::constant] type DecayInterval: Get; @@ -103,12 +110,15 @@ pub mod pallet { } #[pallet::error] - pub enum Error {} + pub enum Error { + //Overflow + Overflow, + } #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { - Self::distribute_rewards(n); + Self::execute_on_init(n); //TODO benchmark this weight properly ::WeightInfo::on_initialize() } @@ -127,7 +137,10 @@ pub mod pallet { #[pallet::getter(fn rewards_percentage)] pub(super) type RewardsPercentage = StorageValue< _, - BoundedVec<(CurrencyId, Perquintill), ::MaxCurrencies>, + BoundedVec< + (::Currency, Perquintill), + ::MaxCurrencies, + >, OptionQuery, >; @@ -153,7 +166,7 @@ pub mod pallet { } impl Pallet { - pub fn distribute_rewards(height: T::BlockNumber) { + pub fn execute_on_init(height: T::BlockNumber) { //get reward per block let reward_per_block = RewardPerBlock::::get(); if reward_per_block == None { @@ -168,69 +181,69 @@ impl Pallet { //update the reward per block if decay interval passed let rewards_adapted_at = RewardsAdaptedAt::::get(); if ext::security::parachain_block_expired::( - rewards_adapted_at.expect("checked for none"), + rewards_adapted_at.expect("HANDLE"), T::DecayInterval::get() - T::BlockNumber::one(), ) .unwrap() { let decay_rate = T::DecayRate::get(); - let reward_this_block = - decay_rate.mul_floor(reward_per_block.expect("checked for none")); - RewardPerBlock::::set(Some(new_reward_per_block)); + reward_this_block = decay_rate.mul_floor(reward_per_block.expect("checked for none")); + RewardPerBlock::::set(Some(reward_this_block)); RewardsAdaptedAt::::set(Some(height)); } - let total_reward_stake = Self::caculate_reward_stake_percentages(reward_this_block).unwrap(); - - + //TODO how to handle error if on init cannot fail? + let _ = Self::distribute_rewards(T::NativeToken::get(), reward_this_block); } //fetch total stake (all), and calulate total usd stake in percentage across pools - fn caculate_reward_stake_percentages() -> Result<(), ()> { + fn distribute_rewards( + reward_currency: T::Currency, + reward_amount: BalanceOf, + ) -> Result<(), DispatchError> { let total_stakes = T::VaultRewards::get_total_stake_all_pools().unwrap(); let total_stake_in_usd = BalanceOf::::default(); for (currency_id, stake) in total_stakes.clone().into_iter() { - let stake_in_usd = T::OracleApi::currency_to_usd(stake, currency_id).unwrap(); + let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id).unwrap(); total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } - Ok(total_stake_in_usd) - let percentages_vec: Vec::new(); + let mut percentages_vec = Vec::<(T::Currency, Perquintill)>::new(); for (currency_id, stake) in total_stakes.into_iter() { - let stake_in_amount = Amount::::new(stake, currency_id); - let stake_in_usd = stake_in_amount.convert_to(::BaseCurrency::get())?; - let percentage = Perquintill::from_rational(stake_in_usd.amount(), total_stake_in_usd); + let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; + let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); + + let reward_for_pool = percentage.mul_floor(reward_amount); + let error_reward_accum = BalanceOf::::zero(); + if T::VaultRewards::distribute_reward( + ¤cy_id, + reward_currency.clone(), + reward_for_pool, + ) + .is_err() + { + error_reward_accum.checked_add(&reward_for_pool).ok_or(Error::::Overflow)?; + } percentages_vec.push((currency_id, percentage)) } - let bounded_vec = BoundedVec::try_from(percentages_vec).expect("should not be longer than max currencies"); - - RewardsPercentage::::set(bounded_vec); - } + //we store the calculated percentages which are good as long as + //prices are unchanged + let bounded_vec = BoundedVec::try_from(percentages_vec) + .expect("should not be longer than max currencies"); + RewardsPercentage::::set(Some(bounded_vec)); - pub fn distribute_rewards(reward)-> Result<(),(){ - // multiply with floor or ceil? - - let stake_percentages = RewardsPercentage::::get(); - - for (currency_id, stake) in stake_percentages.into_iter() { - let reward_for_pool = percentage.mul_floor(reward.amount()); - let error_reward_accum = Amount::::zero(reward.currency()); - if T::VaultRewards::distribute_reward(¤cy_id, reward.currency(), reward_for_pool) - .is_err() - { - error_reward_accum.checked_add(&reward)?; - } - } - + Ok(()) } - } - - +//TODO I actually want this api coming form oracle (defined and implemented) +//but importing oracle gives an unexpected and unrelated error pub trait ToUsdApi { - fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result; + fn currency_to_usd( + amount: &Balance, + currency_id: &CurrencyId, + ) -> Result; } From 928320257423114a00d26f9250bbb18ab3d08f17 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 13 Oct 2023 12:01:10 -0300 Subject: [PATCH 18/52] rewards distribution api with fee connection --- Cargo.lock | 1 + pallets/fee/Cargo.toml | 2 ++ pallets/fee/src/ext.rs | 17 +++++++++++ pallets/fee/src/lib.rs | 18 ++++++----- pallets/reward-distribution/src/lib.rs | 42 ++++++++++++++++++++------ 5 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 pallets/fee/src/ext.rs diff --git a/Cargo.lock b/Cargo.lock index 8342cb0e0..51faa0290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2156,6 +2156,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", + "reward-distribution", "scale-info", "security", "serde", diff --git a/pallets/fee/Cargo.toml b/pallets/fee/Cargo.toml index cdf5f66b6..3b3e563dd 100644 --- a/pallets/fee/Cargo.toml +++ b/pallets/fee/Cargo.toml @@ -27,6 +27,8 @@ currency = { path = "../currency", default-features = false } security = { path = "../security", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking", default-features = false } +reward-distribution = { path = "../reward-distribution", default-features = false } + primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } diff --git a/pallets/fee/src/ext.rs b/pallets/fee/src/ext.rs new file mode 100644 index 000000000..a5c6262c2 --- /dev/null +++ b/pallets/fee/src/ext.rs @@ -0,0 +1,17 @@ +#[cfg(test)] +use mocktopus::macros::mockable; + +#[cfg_attr(test, mockable)] +pub(crate) mod reward_distribution { + use crate::DispatchError; + use currency::Amount; + use reward_distribution::DistributeRewardsToPool; + + pub fn distribute_rewards( + reward: &Amount, + ) -> Result, DispatchError> { + let undistributed_balance = + T::DistributePool::distribute_rewards(reward.amount(), reward.currency())?; + Ok(Amount::::new(undistributed_balance, reward.currency())) + } +} diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index 3f8ce4a21..c864d930c 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -6,6 +6,8 @@ #[cfg(test)] extern crate mocktopus; +#[cfg(test)] +use mocktopus::macros::mockable; use codec::EncodeLike; use frame_support::{ @@ -13,8 +15,6 @@ use frame_support::{ traits::Get, transactional, PalletId, }; -#[cfg(test)] -use mocktopus::macros::mockable; use sp_arithmetic::{traits::*, FixedPointNumber, FixedPointOperand}; use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned}; use sp_std::{ @@ -40,6 +40,8 @@ mod tests; pub mod types; +mod ext; + #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; @@ -108,6 +110,12 @@ pub mod pallet { CurrencyId, >; + /// Pooled rewards distribution Interface + type DistributePool: reward_distribution::DistributeRewardsToPool< + BalanceOf, + CurrencyId, + >; + /// Vault staking pool. type VaultStaking: staking::Staking< DefaultVaultId, @@ -485,11 +493,7 @@ impl Pallet { } fn distribute(reward: &Amount) -> Result, DispatchError> { - //TODO INTERFACE REQUIRED - //From reward-distribution pallet we will pass the reward in wrapped token and - //it will be in charge of distributing to all pools by it's stake in usd - - Ok(Amount::::zero(reward.currency())) + ext::reward_distribution::distribute_rewards::(reward) } pub fn distribute_from_reward_pool< diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 86e4c13fb..714629753 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -23,7 +23,7 @@ use sp_runtime::{ traits::{AtLeast32BitUnsigned, CheckedAdd, One, Zero}, FixedPointOperand, }; -use sp_std::fmt::Debug; +use sp_std::{fmt::Debug, vec::Vec}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -91,7 +91,7 @@ pub mod pallet { type OracleApi: ToUsdApi; - type NativeToken: Get; + type GetNativeCurrencyId: Get; /// Defines the interval (in number of blocks) at which the reward per block decays. #[pallet::constant] @@ -181,27 +181,29 @@ impl Pallet { //update the reward per block if decay interval passed let rewards_adapted_at = RewardsAdaptedAt::::get(); if ext::security::parachain_block_expired::( - rewards_adapted_at.expect("HANDLE"), + rewards_adapted_at.expect("should exists"), T::DecayInterval::get() - T::BlockNumber::one(), ) .unwrap() { let decay_rate = T::DecayRate::get(); - reward_this_block = decay_rate.mul_floor(reward_per_block.expect("checked for none")); + reward_this_block = decay_rate.mul_floor(reward_per_block.expect("should exists")); RewardPerBlock::::set(Some(reward_this_block)); RewardsAdaptedAt::::set(Some(height)); } //TODO how to handle error if on init cannot fail? - let _ = Self::distribute_rewards(T::NativeToken::get(), reward_this_block); + let _ = Self::distribute_rewards(reward_this_block, T::GetNativeCurrencyId::get()); } //fetch total stake (all), and calulate total usd stake in percentage across pools + //distribute the reward accoridigly fn distribute_rewards( - reward_currency: T::Currency, reward_amount: BalanceOf, - ) -> Result<(), DispatchError> { + reward_currency: T::Currency, + ) -> Result, DispatchError> { + //calculate the total stake across all collateral pools in USD let total_stakes = T::VaultRewards::get_total_stake_all_pools().unwrap(); let total_stake_in_usd = BalanceOf::::default(); for (currency_id, stake) in total_stakes.clone().into_iter() { @@ -209,13 +211,15 @@ impl Pallet { total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } + //distribute the rewards to each collateral pool let mut percentages_vec = Vec::<(T::Currency, Perquintill)>::new(); + let mut error_reward_accum = BalanceOf::::zero(); for (currency_id, stake) in total_stakes.into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); let reward_for_pool = percentage.mul_floor(reward_amount); - let error_reward_accum = BalanceOf::::zero(); + if T::VaultRewards::distribute_reward( ¤cy_id, reward_currency.clone(), @@ -223,7 +227,8 @@ impl Pallet { ) .is_err() { - error_reward_accum.checked_add(&reward_for_pool).ok_or(Error::::Overflow)?; + error_reward_accum = + error_reward_accum.checked_add(&reward_for_pool).ok_or(Error::::Overflow)?; } percentages_vec.push((currency_id, percentage)) @@ -235,7 +240,7 @@ impl Pallet { .expect("should not be longer than max currencies"); RewardsPercentage::::set(Some(bounded_vec)); - Ok(()) + Ok(error_reward_accum) } } @@ -247,3 +252,20 @@ pub trait ToUsdApi { currency_id: &CurrencyId, ) -> Result; } + +//Distribute Rewards interface +pub trait DistributeRewardsToPool { + fn distribute_rewards( + amount: Balance, + currency_id: CurrencyId, + ) -> Result; +} + +impl DistributeRewardsToPool, T::Currency> for Pallet { + fn distribute_rewards( + amount: BalanceOf, + currency_id: T::Currency, + ) -> Result, DispatchError> { + Pallet::::distribute_rewards(amount, currency_id) + } +} From 4d2bf4e975e8a46e263fafefcd23b2ac60eb8235 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 13 Oct 2023 12:26:09 -0300 Subject: [PATCH 19/52] better error handling in on initialize --- Cargo.lock | 1 + pallets/reward-distribution/Cargo.toml | 1 + pallets/reward-distribution/src/lib.rs | 44 ++++++++++++++++---------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51faa0290..4e970561b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6870,6 +6870,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "pooled-rewards", diff --git a/pallets/reward-distribution/Cargo.toml b/pallets/reward-distribution/Cargo.toml index 0c9f91320..3b61aeacf 100644 --- a/pallets/reward-distribution/Cargo.toml +++ b/pallets/reward-distribution/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.2.0", default-features = false, features = ["derive"] } serde = { version = "1.0.130", default-features = false, features = ["derive"], optional = true } +log = {version = "0.4.14", default-features = false} # Substrate dependencies sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 714629753..d7fb8e1f5 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -168,29 +168,41 @@ pub mod pallet { impl Pallet { pub fn execute_on_init(height: T::BlockNumber) { //get reward per block - let reward_per_block = RewardPerBlock::::get(); - if reward_per_block == None { - return - } + let reward_per_block = match RewardPerBlock::::get() { + Some(value) => value, + None => { + log::warn!("Reward per block is None"); + return + }, + }; if let Err(_) = ext::security::ensure_parachain_status_running::() { return } - let mut reward_this_block = reward_per_block.expect("checked for none"); + let rewards_adapted_at = match RewardsAdaptedAt::::get() { + Some(value) => value, + None => { + log::warn!("RewardsAdaptedAt is None"); + return + }, + }; + + let mut reward_this_block = reward_per_block; + //update the reward per block if decay interval passed - let rewards_adapted_at = RewardsAdaptedAt::::get(); - if ext::security::parachain_block_expired::( - rewards_adapted_at.expect("should exists"), + if let Ok(expired) = ext::security::parachain_block_expired::( + rewards_adapted_at, T::DecayInterval::get() - T::BlockNumber::one(), - ) - .unwrap() - { - let decay_rate = T::DecayRate::get(); - reward_this_block = decay_rate.mul_floor(reward_per_block.expect("should exists")); - RewardPerBlock::::set(Some(reward_this_block)); - - RewardsAdaptedAt::::set(Some(height)); + ) { + if expired { + let decay_rate = T::DecayRate::get(); + reward_this_block = decay_rate.mul_floor(reward_per_block); + RewardPerBlock::::set(Some(reward_this_block)); + RewardsAdaptedAt::::set(Some(height)); + } + } else { + log::warn!("Failed to check if the parachain block expired"); } //TODO how to handle error if on init cannot fail? From 46106834837d526d1f66026a1a54f99b21f4a948 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 13 Oct 2023 17:00:33 -0300 Subject: [PATCH 20/52] remove temporary price api, added OracleApi from oracle module. Added new configuration to necessary mock runtimes --- Cargo.lock | 8 +++ pallets/fee/Cargo.toml | 3 ++ pallets/fee/src/mock.rs | 43 ++++++++++++++-- pallets/issue/Cargo.toml | 2 + pallets/issue/src/mock.rs | 39 +++++++++++++- pallets/nomination/Cargo.toml | 2 + pallets/nomination/src/mock.rs | 38 +++++++++++++- pallets/oracle/src/lib.rs | 14 ++++- pallets/redeem/Cargo.toml | 2 + pallets/redeem/src/mock.rs | 37 +++++++++++++- pallets/replace/Cargo.toml | 2 + pallets/replace/src/mock.rs | 37 +++++++++++++- pallets/reward-distribution/Cargo.toml | 9 ++++ .../reward-distribution/src/benchmarking.rs | 16 +++--- pallets/reward-distribution/src/lib.rs | 40 +++++++-------- pallets/reward-distribution/src/mock.rs | 51 +++++++++++++++++-- pallets/reward-distribution/src/tests.rs | 1 + pallets/reward-distribution/src/types.rs | 3 -- pallets/vault-registry/Cargo.toml | 1 + pallets/vault-registry/src/mock.rs | 38 +++++++++++++- testchain/runtime/src/lib.rs | 25 ++++++++- 21 files changed, 359 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e970561b..dfb3b7e29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2149,6 +2149,7 @@ dependencies = [ "frame-support", "frame-system", "mocktopus", + "oracle", "orml-currencies", "orml-tokens", "orml-traits", @@ -3488,6 +3489,7 @@ dependencies = [ "parity-scale-codec", "pooled-rewards", "reward", + "reward-distribution", "scale-info", "security", "serde", @@ -5134,6 +5136,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", + "reward-distribution", "scale-info", "security", "serde", @@ -6619,6 +6622,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", + "reward-distribution", "scale-info", "security", "serde", @@ -6779,6 +6783,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "pooled-rewards", + "reward-distribution", "scale-info", "security", "serde", @@ -6871,6 +6876,8 @@ dependencies = [ "frame-support", "frame-system", "log", + "mocktopus", + "oracle", "pallet-balances", "parity-scale-codec", "pooled-rewards", @@ -10913,6 +10920,7 @@ dependencies = [ "pooled-rewards", "pretty_assertions", "reward", + "reward-distribution", "scale-info", "security", "serde", diff --git a/pallets/fee/Cargo.toml b/pallets/fee/Cargo.toml index 3b3e563dd..39954a7ee 100644 --- a/pallets/fee/Cargo.toml +++ b/pallets/fee/Cargo.toml @@ -28,6 +28,7 @@ security = { path = "../security", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking", default-features = false } reward-distribution = { path = "../reward-distribution", default-features = false } +oracle = { path = "../oracle", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -67,6 +68,8 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "reward-distribution/std", + "oracle/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index 7b665a715..19601ed64 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ parameter_types, - traits::{ConstU32, Everything}, + traits::{ConstU32, ConstU64, Everything}, PalletId, }; use mocktopus::mocking::clear_mocks; @@ -11,18 +11,17 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, Zero}, - FixedPointNumber, + DispatchError, FixedPointNumber, Perquintill, }; +use crate as fee; +use crate::{Config, Error}; pub use currency::testing_constants::{ DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, DEFAULT_WRAPPED_CURRENCY, }; pub use primitives::CurrencyId; use primitives::VaultId; -use crate as fee; -use crate::{Config, Error}; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -43,6 +42,7 @@ frame_support::construct_runtime!( Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, Staking: staking::{Pallet, Storage, Event}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, @@ -226,6 +226,38 @@ parameter_types! { pub const MaxExpectedValue: UnsignedFixedPoint = UnsignedFixedPoint::from_inner(::DIV); } +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; +} + impl Config for Test { type FeePalletId = FeePalletId; type WeightInfo = fee::SubstrateWeight; @@ -237,6 +269,7 @@ impl Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/issue/Cargo.toml b/pallets/issue/Cargo.toml index a914604b5..e23b88000 100644 --- a/pallets/issue/Cargo.toml +++ b/pallets/issue/Cargo.toml @@ -34,6 +34,7 @@ security = { path = "../security", default-features = false } stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } +reward-distribution = { path = "../reward-distribution", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -86,6 +87,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "reward-distribution/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index fa92c96db..2f473bcc6 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use frame_support::{ assert_ok, parameter_types, - traits::{ConstU32, Everything, GenesisBuild}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, PalletId, }; use mocktopus::{macros::mockable, mocking::clear_mocks}; @@ -19,6 +19,7 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero}, + DispatchError, Perquintill, }; pub use currency::{ @@ -51,6 +52,8 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Tokens: orml_tokens::{Pallet, Storage, Config, Event}, Currencies: orml_currencies::{Pallet, Call}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, // Operational Currency: currency::{Pallet}, @@ -60,7 +63,6 @@ frame_support::construct_runtime!( Oracle: oracle::{Pallet, Call, Config, Storage, Event}, Fee: fee::{Pallet, Call, Config, Storage}, Staking: staking::{Pallet, Storage, Event}, - Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, } ); @@ -302,6 +304,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; } parameter_types! { @@ -317,6 +320,38 @@ impl pooled_rewards::Config for Test { type MaxRewardCurrencies = MaxRewardCurrencies; } +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; +} + parameter_types! { pub const MinimumPeriod: Moment = 5; } diff --git a/pallets/nomination/Cargo.toml b/pallets/nomination/Cargo.toml index 09b449a6b..52efa0d28 100644 --- a/pallets/nomination/Cargo.toml +++ b/pallets/nomination/Cargo.toml @@ -30,6 +30,7 @@ fee = { path = "../fee", default-features = false } oracle = { path = "../oracle", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } staking = { path = "../staking", default-features = false } +reward-distribution = { path = "../reward-distribution", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -76,6 +77,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "reward-distribution/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index 15f4dc013..a1eb9bd27 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ assert_ok, parameter_types, - traits::{ConstU32, Everything, GenesisBuild}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, PalletId, }; use mocktopus::{macros::mockable, mocking::clear_mocks}; @@ -17,7 +17,7 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero}, - FixedPointNumber, + DispatchError, FixedPointNumber, Perquintill, }; use std::cell::RefCell; @@ -51,6 +51,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, @@ -266,6 +267,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; } impl oracle::Config for Test { @@ -295,6 +297,38 @@ impl pooled_rewards::Config for Test { type MaxRewardCurrencies = MaxRewardCurrencies; } +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; +} + impl Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = crate::SubstrateWeight; diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index 1ead175ec..fbb12b0c9 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -369,5 +369,17 @@ impl Pallet { } pub trait OracleApi { - fn currency_to_usd(amount: Balance, currency_id: CurrencyId) -> Result; + fn currency_to_usd( + amount: &Balance, + currency_id: &CurrencyId, + ) -> Result; +} + +impl OracleApi, CurrencyId> for Pallet { + fn currency_to_usd( + amount: &BalanceOf, + currency_id: &CurrencyId, + ) -> Result, DispatchError> { + Pallet::::currency_to_usd(amount.clone(), currency_id.clone()) + } } diff --git a/pallets/redeem/Cargo.toml b/pallets/redeem/Cargo.toml index 57ce39932..e01c56814 100644 --- a/pallets/redeem/Cargo.toml +++ b/pallets/redeem/Cargo.toml @@ -31,6 +31,7 @@ oracle = { path = "../oracle", default-features = false } security = { path = "../security", default-features = false } stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } +reward-distribution = { path = "../reward-distribution", default-features = false } primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -82,6 +83,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "reward-distribution/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index 977e49f90..0fb257f75 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ assert_ok, parameter_types, - traits::{ConstU32, Everything, GenesisBuild}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, PalletId, }; use mocktopus::{macros::mockable, mocking::clear_mocks}; @@ -20,6 +20,7 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, IdentityLookup, One, Zero}, + DispatchError, Perquintill, }; pub use currency::{ @@ -54,6 +55,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, // Operational StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event}, @@ -333,6 +335,39 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; +} + +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; } impl Config for Test { diff --git a/pallets/replace/Cargo.toml b/pallets/replace/Cargo.toml index 72a617e5d..91e92aafe 100644 --- a/pallets/replace/Cargo.toml +++ b/pallets/replace/Cargo.toml @@ -34,6 +34,7 @@ security = { path = "../security", default-features = false } stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } +reward-distribution = { path = "../reward-distribution", default-features = false } # Orml dependencies orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false } @@ -85,6 +86,7 @@ std = [ "orml-currencies/std", "orml-tokens/std", "orml-traits/std", + "reward-distribution/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index 880462135..958b37b59 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ assert_ok, parameter_types, - traits::{ConstU32, Everything, GenesisBuild}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, PalletId, }; use mocktopus::{macros::mockable, mocking::clear_mocks}; @@ -17,6 +17,7 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero}, + DispatchError, Perquintill, }; use std::cell::RefCell; @@ -52,6 +53,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, // Operational StellarRelay: stellar_relay::{Pallet, Call, Config, Storage, Event}, @@ -330,6 +332,39 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; +} + +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; } impl Config for Test { diff --git a/pallets/reward-distribution/Cargo.toml b/pallets/reward-distribution/Cargo.toml index 3b61aeacf..1f859ad11 100644 --- a/pallets/reward-distribution/Cargo.toml +++ b/pallets/reward-distribution/Cargo.toml @@ -28,6 +28,11 @@ primitives = { package = "spacewalk-primitives", path = "../../primitives", defa security = { path = "../security", default-features = false } currency = { path = "../currency", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } +oracle = { path = "../oracle", default-features = false } + +[dev-dependencies] +mocktopus = "0.8.0" + [features] default = ["std"] @@ -44,6 +49,10 @@ std = [ "frame-system/std", "frame-benchmarking/std", "pallet-balances/std", + "currency/std", + "pooled-rewards/std", + "oracle/std", + "primitives/std", "security/std", ] runtime-benchmarks = [ diff --git a/pallets/reward-distribution/src/benchmarking.rs b/pallets/reward-distribution/src/benchmarking.rs index af4879aa8..5de9fed10 100644 --- a/pallets/reward-distribution/src/benchmarking.rs +++ b/pallets/reward-distribution/src/benchmarking.rs @@ -1,10 +1,11 @@ -use super::*; -use frame_benchmarking::v2::{benchmarks, impl_benchmark_test_suite}; -use frame_system::RawOrigin; -use sp_std::vec; - #[allow(unused)] use super::Pallet as RewardDistribution; +use super::*; +use frame_benchmarking::{ + v2::{benchmarks, impl_benchmark_test_suite}, + vec, +}; +use frame_system::RawOrigin; #[benchmarks] pub mod benchmarks { @@ -20,11 +21,6 @@ pub mod benchmarks { assert_eq!(RewardDistribution::::reward_per_block(), Some(new_reward_per_block)); } - #[benchmark] - fn on_initialize() { - Timestamp::::set_timestamp(1000u32.into()); - } - impl_benchmark_test_suite!( RewardDistribution, crate::mock::ExtBuilder::build(), diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index d7fb8e1f5..b073ab7d2 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -7,18 +7,15 @@ mod default_weights; -use crate::types::{AccountIdOf, BalanceOf, DefaultVaultId}; +use crate::types::{BalanceOf, DefaultVaultId}; use codec::{FullCodec, FullEncode, MaxEncodedLen}; pub use default_weights::{SubstrateWeight, WeightInfo}; use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::DispatchError, - traits::{Currency, Get}, - transactional, + dispatch::DispatchResult, pallet_prelude::DispatchError, traits::Get, transactional, BoundedVec, }; +use oracle::OracleApi; use pooled_rewards::RewardsApi; use sp_arithmetic::Perquintill; -use sp_core::bounded_vec::BoundedVec; use sp_runtime::{ traits::{AtLeast32BitUnsigned, CheckedAdd, One, Zero}, FixedPointOperand, @@ -59,7 +56,9 @@ pub mod pallet { type WeightInfo: WeightInfo; /// The currency trait. - type Currency: Currency> + type Currency: Parameter + + Member + + Copy + Clone + Decode + FullEncode @@ -89,7 +88,8 @@ pub mod pallet { type MaxCurrencies: Get; - type OracleApi: ToUsdApi; + //type OracleApi: ToUsdApi; + type OracleApi: oracle::OracleApi; type GetNativeCurrencyId: Get; @@ -162,6 +162,16 @@ pub mod pallet { Self::deposit_event(Event::::RewardPerBlockAdapted(new_reward_per_block)); Ok(()) } + + // #[pallet::call_index(1)] + // #[pallet::weight(::WeightInfo::set_reward_per_block())] + // #[transactional] + // pub fn collect_rewards( + // origin: OriginFor, + // collateral_asset: T::Currency, + // ) -> DispatchResult { + // let nominator_id = ensure_signed(origin)?; + // } } } @@ -216,10 +226,10 @@ impl Pallet { reward_currency: T::Currency, ) -> Result, DispatchError> { //calculate the total stake across all collateral pools in USD - let total_stakes = T::VaultRewards::get_total_stake_all_pools().unwrap(); + let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; let total_stake_in_usd = BalanceOf::::default(); for (currency_id, stake) in total_stakes.clone().into_iter() { - let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id).unwrap(); + let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } @@ -255,16 +265,6 @@ impl Pallet { Ok(error_reward_accum) } } - -//TODO I actually want this api coming form oracle (defined and implemented) -//but importing oracle gives an unexpected and unrelated error -pub trait ToUsdApi { - fn currency_to_usd( - amount: &Balance, - currency_id: &CurrencyId, - ) -> Result; -} - //Distribute Rewards interface pub trait DistributeRewardsToPool { fn distribute_rewards( diff --git a/pallets/reward-distribution/src/mock.rs b/pallets/reward-distribution/src/mock.rs index 315edfef9..41437bf21 100644 --- a/pallets/reward-distribution/src/mock.rs +++ b/pallets/reward-distribution/src/mock.rs @@ -8,13 +8,18 @@ use sp_core::H256; use sp_runtime::{ generic::Header as GenericHeader, traits::{BlakeTwo256, IdentityLookup}, - Perquintill, + DispatchError, Perquintill, }; +use sp_arithmetic::FixedI128; + +use primitives::{Balance, CurrencyId, VaultId}; type Header = GenericHeader; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +pub use currency::testing_constants::DEFAULT_NATIVE_CURRENCY; + // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( pub enum Test where @@ -26,13 +31,15 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Security: security::{Pallet, Call, Storage, Event}, RewardDistribution: reward_distribution::{Pallet, Call, Storage, Event}, + + Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, } ); -pub type AccountId = u64; -pub type Balance = u128; pub type BlockNumber = u64; pub type Index = u64; +pub type AccountId = u64; +pub type SignedFixedPoint = FixedI128; parameter_types! { pub const BlockHashCount: u64 = 250; @@ -95,12 +102,48 @@ parameter_types! { pub const DecayRate: Perquintill = Perquintill::from_percent(5); } +parameter_types! { + pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; + pub const MaxCurrencies: u32 = 10; +} + +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + +impl pooled_rewards::Config for Test { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type PoolId = CurrencyId; + type PoolRewardsCurrencyId = CurrencyId; + type StakeId = VaultId; + type MaxRewardCurrencies = MaxRewardCurrencies; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = crate::default_weights::SubstrateWeight; - type Currency = Balances; + type Currency = CurrencyId; + type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; } pub struct ExtBuilder; diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index 4c0c4409b..23c732fa0 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -1,5 +1,6 @@ use crate::mock::*; use frame_support::{assert_err, assert_ok}; + use sp_runtime::DispatchError::BadOrigin; #[cfg(test)] diff --git a/pallets/reward-distribution/src/types.rs b/pallets/reward-distribution/src/types.rs index 44989dc5e..0b128f640 100644 --- a/pallets/reward-distribution/src/types.rs +++ b/pallets/reward-distribution/src/types.rs @@ -1,9 +1,6 @@ use crate::Config; -//use currency::CurrencyId; use primitives::{CurrencyId, VaultId}; -pub type AccountIdOf = ::AccountId; - pub(crate) type BalanceOf = ::Balance; pub(crate) type DefaultVaultId = VaultId<::AccountId, CurrencyId>; diff --git a/pallets/vault-registry/Cargo.toml b/pallets/vault-registry/Cargo.toml index 1e3fa5593..cbc99e998 100644 --- a/pallets/vault-registry/Cargo.toml +++ b/pallets/vault-registry/Cargo.toml @@ -35,6 +35,7 @@ primitives = {package = "spacewalk-primitives", path = "../../primitives", defau reward = {path = "../reward", default-features = false} security = {path = "../security", default-features = false} staking = {path = "../staking", default-features = false} +reward-distribution = {path = "../reward-distribution", default-features = false} pooled-rewards = { path = "../pooled-rewards", default-features = false } diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index 20d2dc167..354b2b3ab 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ parameter_types, - traits::{ConstU32, Everything, GenesisBuild}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, PalletId, }; use mocktopus::{macros::mockable, mocking::clear_mocks}; @@ -17,6 +17,7 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero}, + DispatchError, Perquintill, }; use std::cell::RefCell; @@ -49,6 +50,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + RewardDistribution: reward_distribution::{Pallet, Storage, Event}, // Operational Security: security::{Pallet, Call, Storage, Event}, @@ -57,6 +59,7 @@ frame_support::construct_runtime!( Staking: staking::{Pallet, Storage, Event}, Fee: fee::{Pallet, Call, Config, Storage}, Currency: currency::{Pallet}, + } ); @@ -247,6 +250,39 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; +} + +parameter_types! { + pub const DecayRate: Perquintill = Perquintill::from_percent(5); + pub const MaxCurrencies: u32 = 10; +} + +pub struct OracleApiMock {} +impl oracle::OracleApi for OracleApiMock { + fn currency_to_usd( + _amount: &Balance, + currency_id: &CurrencyId, + ) -> Result { + let _native_currency = GetNativeCurrencyId::get(); + match currency_id { + _native_currency => return Ok(100), + //_ => unimplemented!("unimplemented mock conversion for currency"), + } + } +} + +impl reward_distribution::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = reward_distribution::SubstrateWeight; + type Currency = CurrencyId; + type Balance = Balance; + type DecayInterval = ConstU64<100>; + type DecayRate = DecayRate; + type VaultRewards = Rewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = OracleApiMock; } parameter_types! { diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index 8daa54c5d..f87cbb530 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -549,6 +549,7 @@ impl fee::Config for Runtime { type VaultStaking = VaultStaking; type OnSweep = currency::SweepFunds; type MaxExpectedValue = MaxExpectedValue; + type DistributePool = RewardDistribution; } impl nomination::Config for Runtime { @@ -565,14 +566,34 @@ impl clients_info::Config for Runtime { parameter_types! { pub const DecayRate: Perquintill = Perquintill::from_percent(5); -} + pub const MaxCurrencies: u32 = 10; +} + +// pub struct OracleApi {} +// impl reward_distribution::ToUsdApi for OracleApi { +// fn currency_to_usd( +// _amount: &Balance, +// currency_id: &CurrencyId, +// ) -> Result { +// let _native_currency = GetNativeCurrencyId::get(); +// match currency_id { +// _native_currency => return Ok(100), +// //_ => unimplemented!("unimplemented mock conversion for currency"), +// } +// } +// } impl reward_distribution::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = Balances; + type Currency = CurrencyId; + type Balance = Balance; type DecayInterval = ConstU32<100>; type DecayRate = DecayRate; + type VaultRewards = VaultRewards; + type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxCurrencies = MaxCurrencies; + type OracleApi = Oracle; } parameter_types! { From 5950f2f84f73e6f231e4a5bce5406d334462f73a Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 13 Oct 2023 19:12:27 -0300 Subject: [PATCH 21/52] set up basic tests for distribution --- pallets/reward-distribution/src/ext.rs | 20 +++++++++++ pallets/reward-distribution/src/lib.rs | 22 ++++++++----- pallets/reward-distribution/src/mock.rs | 20 ++++++----- pallets/reward-distribution/src/tests.rs | 42 ++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index b35ffd5b1..6024e657f 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -17,3 +17,23 @@ pub(crate) mod security { >::parachain_block_expired(opentime, period) } } + +#[cfg_attr(test, mockable)] +pub(crate) mod pooled_rewards { + use frame_support::pallet_prelude::DispatchResult; + use pooled_rewards::RewardsApi; + use sp_runtime::DispatchError; + + pub fn get_total_stake_all_pools( + ) -> Result, DispatchError> { + T::VaultRewards::get_total_stake_all_pools() + } + + pub fn distribute_reward( + pool_id: &T::Currency, + reward_currency: &T::Currency, + amount: T::Balance, + ) -> DispatchResult { + T::VaultRewards::distribute_reward(pool_id, reward_currency.clone(), amount) + } +} diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index b073ab7d2..c64e8f161 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -14,7 +14,6 @@ use frame_support::{ dispatch::DispatchResult, pallet_prelude::DispatchError, traits::Get, transactional, BoundedVec, }; use oracle::OracleApi; -use pooled_rewards::RewardsApi; use sp_arithmetic::Perquintill; use sp_runtime::{ traits::{AtLeast32BitUnsigned, CheckedAdd, One, Zero}, @@ -172,6 +171,14 @@ pub mod pallet { // ) -> DispatchResult { // let nominator_id = ensure_signed(origin)?; // } + + //input (account_id, collateral_asset_id, Optional(reward_currency_id)) + //Expectation: transfer the value of rewards that an account_id has + // + //get from new fx the list of vaults per account id + //execute withdraw on pool rewards for each vault_id (vault_id.collateral, + // vault_id, reward_currency_id) + //transfer rewards from stake } } @@ -226,25 +233,24 @@ impl Pallet { reward_currency: T::Currency, ) -> Result, DispatchError> { //calculate the total stake across all collateral pools in USD - let total_stakes = T::VaultRewards::get_total_stake_all_pools()?; - let total_stake_in_usd = BalanceOf::::default(); + let total_stakes = ext::pooled_rewards::get_total_stake_all_pools::()?; + let mut total_stake_in_usd = BalanceOf::::default(); for (currency_id, stake) in total_stakes.clone().into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; - total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); + total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } - + println!("{:?}", total_stake_in_usd); //distribute the rewards to each collateral pool let mut percentages_vec = Vec::<(T::Currency, Perquintill)>::new(); let mut error_reward_accum = BalanceOf::::zero(); for (currency_id, stake) in total_stakes.into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); - let reward_for_pool = percentage.mul_floor(reward_amount); - if T::VaultRewards::distribute_reward( + if ext::pooled_rewards::distribute_reward::( ¤cy_id, - reward_currency.clone(), + &reward_currency, reward_for_pool, ) .is_err() diff --git a/pallets/reward-distribution/src/mock.rs b/pallets/reward-distribution/src/mock.rs index 41437bf21..db0189914 100644 --- a/pallets/reward-distribution/src/mock.rs +++ b/pallets/reward-distribution/src/mock.rs @@ -4,6 +4,8 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64, Everything}, }; +use primitives::CurrencyId::XCM; +use sp_arithmetic::FixedI128; use sp_core::H256; use sp_runtime::{ generic::Header as GenericHeader, @@ -11,8 +13,6 @@ use sp_runtime::{ DispatchError, Perquintill, }; -use sp_arithmetic::FixedI128; - use primitives::{Balance, CurrencyId, VaultId}; type Header = GenericHeader; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -123,14 +123,18 @@ impl pooled_rewards::Config for Test { pub struct OracleApiMock {} impl oracle::OracleApi for OracleApiMock { fn currency_to_usd( - _amount: &Balance, + amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); - match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), - } + let amount_in_usd = match currency_id { + &XCM(0) => amount * 100, + &XCM(1) => amount * 10, + &XCM(2) => amount * 15, + &XCM(3) => amount * 20, + &XCM(4) => amount * 35, + _ => amount * 50, + }; + Ok(amount_in_usd) } } impl Config for Test { diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index 23c732fa0..90704094f 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -1,8 +1,17 @@ -use crate::mock::*; +use crate::{ext, mock::*, pallet}; use frame_support::{assert_err, assert_ok}; - +use mocktopus::mocking::*; use sp_runtime::DispatchError::BadOrigin; +pub use currency::testing_constants::DEFAULT_COLLATERAL_CURRENCY; +use primitives::CurrencyId::XCM; + +fn build_total_stakes( +) -> Vec<(::Currency, ::Balance)> { + //total in usd 215000 + vec![(DEFAULT_COLLATERAL_CURRENCY, 1000), (XCM(1), 3000), (XCM(2), 5000), (XCM(3), 500)] +} + #[cfg(test)] #[test] fn test_set_rewards_per_block() { @@ -34,3 +43,32 @@ fn test_set_rewards_per_block() { assert_eq!(RewardDistribution::rewards_adapted_at(), Some(1)); }); } + +#[cfg(test)] +#[test] +fn distributes_rewards_properly() { + run_test(|| { + ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { + let initial_stakes = build_total_stakes(); + MockResult::Return(Ok(initial_stakes)) + }); + + let mut expected_pool_ids = vec![XCM(0), XCM(1), XCM(2), XCM(3)].into_iter(); + let mut expected_stake_per_pool = vec![46, 13, 34, 4].into_iter(); + ext::pooled_rewards::distribute_reward::.mock_safe( + move |pool_id, reward_currency, amount| { + let expected_pool_id = expected_pool_ids.next().expect("More calls than expected"); + let expected_stake_per_this_pool = + expected_stake_per_pool.next().expect("More calls than expected"); + assert_eq!(pool_id, &expected_pool_id); + assert_eq!(amount, expected_stake_per_this_pool); + assert_eq!(reward_currency, &XCM(18)); + MockResult::Return(Ok(())) + }, + ); + + let _ = RewardDistribution::distribute_rewards(100, XCM(18)).unwrap(); + + assert_eq!(Some(1), Some(1)); + }); +} From bd9d6c3fc7172c845684f30c28d29441b8f6bc5d Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 16 Oct 2023 11:50:53 -0300 Subject: [PATCH 22/52] added more pallet tests for reward-distribution logic --- pallets/reward-distribution/src/lib.rs | 5 +- pallets/reward-distribution/src/tests.rs | 96 +++++++++++++++++++++++- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index c64e8f161..a5887dc8e 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -213,8 +213,9 @@ impl Pallet { T::DecayInterval::get() - T::BlockNumber::one(), ) { if expired { + println!("updating rewards per block with decay"); let decay_rate = T::DecayRate::get(); - reward_this_block = decay_rate.mul_floor(reward_per_block); + reward_this_block = (Perquintill::one() - decay_rate).mul_floor(reward_per_block); RewardPerBlock::::set(Some(reward_this_block)); RewardsAdaptedAt::::set(Some(height)); } @@ -234,12 +235,12 @@ impl Pallet { ) -> Result, DispatchError> { //calculate the total stake across all collateral pools in USD let total_stakes = ext::pooled_rewards::get_total_stake_all_pools::()?; + let mut total_stake_in_usd = BalanceOf::::default(); for (currency_id, stake) in total_stakes.clone().into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } - println!("{:?}", total_stake_in_usd); //distribute the rewards to each collateral pool let mut percentages_vec = Vec::<(T::Currency, Perquintill)>::new(); let mut error_reward_accum = BalanceOf::::zero(); diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index 90704094f..ced48fc13 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -67,8 +67,98 @@ fn distributes_rewards_properly() { }, ); - let _ = RewardDistribution::distribute_rewards(100, XCM(18)).unwrap(); - - assert_eq!(Some(1), Some(1)); + assert_ok!(RewardDistribution::distribute_rewards(100, XCM(18))); }); } + +#[cfg(test)] +#[test] +fn on_initialize_hook_distribution_works() { + run_test(|| { + ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { + let initial_stakes = build_total_stakes(); + MockResult::Return(Ok(initial_stakes)) + }); + + let mut expected_pool_ids = vec![XCM(0), XCM(1), XCM(2), XCM(3)].into_iter(); + let mut expected_stake_per_pool = vec![46, 13, 34, 4].into_iter(); + ext::pooled_rewards::distribute_reward::.mock_safe( + move |pool_id, reward_currency, amount| { + let expected_pool_id = expected_pool_ids.next().expect("More calls than expected"); + let expected_stake_per_this_pool = + expected_stake_per_pool.next().expect("More calls than expected"); + assert_eq!(pool_id, &expected_pool_id); + assert_eq!(amount, expected_stake_per_this_pool); + assert_eq!(reward_currency, &::GetNativeCurrencyId::get()); + MockResult::Return(Ok(())) + }, + ); + + System::set_block_number(100); + let new_rewards_per_block = 100; + assert_ok!(RewardDistribution::set_reward_per_block( + RuntimeOrigin::root(), + new_rewards_per_block + )); + + assert_eq!(RewardDistribution::rewards_adapted_at(), Some(100)); + + System::set_block_number(101); + RewardDistribution::execute_on_init(101); + }) +} + +#[cfg(test)] +#[test] +fn udpate_reward_does_not_trigger_incorrectly() { + run_test(|| { + ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { + let initial_stakes = build_total_stakes(); + MockResult::Return(Ok(initial_stakes)) + }); + + let new_rewards_per_block = 100; + assert_ok!(RewardDistribution::set_reward_per_block( + RuntimeOrigin::root(), + new_rewards_per_block + )); + + System::set_block_number(2); + RewardDistribution::execute_on_init(2); + + //same reward per block is expected + assert_eq!(pallet::RewardPerBlock::::get(), Some(new_rewards_per_block)); + }) +} + +#[cfg(test)] +#[test] +fn udpate_reward_per_block_works() { + run_test(|| { + ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { + let initial_stakes = build_total_stakes(); + MockResult::Return(Ok(initial_stakes)) + }); + ext::security::parachain_block_expired:: + .mock_safe(move |_, _| MockResult::Return(Ok(true))); + + let new_rewards_per_block = 10000; + assert_ok!(RewardDistribution::set_reward_per_block( + RuntimeOrigin::root(), + new_rewards_per_block + )); + + System::set_block_number(200); + //Security::::set_active_block_number(200); + RewardDistribution::execute_on_init(200); + + //we expect rewards to have fallen by DecayRate percent from initial + assert_eq!(pallet::RewardPerBlock::::get(), Some(9500)); + + System::set_block_number(300); + //Security::::set_active_block_number(300); + RewardDistribution::execute_on_init(300); + //we expect reward to have fallen by DecayRate twice (compound) + assert_eq!(pallet::RewardPerBlock::::get(), Some(9025)); + }) +} From d77918595b9047361ce63e7b7388405722cdb4e0 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 16 Oct 2023 14:23:43 -0300 Subject: [PATCH 23/52] print cleanups, added mock for security chain --- pallets/reward-distribution/src/lib.rs | 1 - pallets/reward-distribution/src/tests.rs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index a5887dc8e..8c1c4e917 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -213,7 +213,6 @@ impl Pallet { T::DecayInterval::get() - T::BlockNumber::one(), ) { if expired { - println!("updating rewards per block with decay"); let decay_rate = T::DecayRate::get(); reward_this_block = (Perquintill::one() - decay_rate).mul_floor(reward_per_block); RewardPerBlock::::set(Some(reward_this_block)); diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index ced48fc13..d91c5cec0 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -117,6 +117,9 @@ fn udpate_reward_does_not_trigger_incorrectly() { MockResult::Return(Ok(initial_stakes)) }); + ext::security::parachain_block_expired:: + .mock_safe(move |_, _| MockResult::Return(Ok(false))); + let new_rewards_per_block = 100; assert_ok!(RewardDistribution::set_reward_per_block( RuntimeOrigin::root(), From 6e9079c040bd17e8ffb4cf181831f9c41a966ac2 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 16 Oct 2023 16:31:07 -0300 Subject: [PATCH 24/52] wip commit --- pallets/reward-distribution/src/ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 6024e657f..7efeab3a5 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -23,7 +23,7 @@ pub(crate) mod pooled_rewards { use frame_support::pallet_prelude::DispatchResult; use pooled_rewards::RewardsApi; use sp_runtime::DispatchError; - + use sp_std::vec::Vec; pub fn get_total_stake_all_pools( ) -> Result, DispatchError> { T::VaultRewards::get_total_stake_all_pools() From 543ccf35699201ae00ac2de445f62d5d4bdd95e7 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 17 Oct 2023 11:06:05 -0300 Subject: [PATCH 25/52] added collect extrinsic, added dependency of currecies config, remove defined currencyid in config --- Cargo.lock | 1 + pallets/fee/src/ext.rs | 4 +- pallets/fee/src/lib.rs | 2 +- pallets/fee/src/mock.rs | 2 +- pallets/issue/src/mock.rs | 2 +- pallets/nomination/src/mock.rs | 2 +- pallets/redeem/src/mock.rs | 2 +- pallets/replace/src/mock.rs | 2 +- pallets/reward-distribution/Cargo.toml | 2 + .../src/default_weights.rs | 10 ++ pallets/reward-distribution/src/ext.rs | 44 +++++- pallets/reward-distribution/src/lib.rs | 127 +++++++++++------- pallets/reward-distribution/src/types.rs | 5 +- pallets/vault-registry/src/mock.rs | 2 +- testchain/runtime/src/lib.rs | 2 +- 15 files changed, 142 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfb3b7e29..5e3e46a51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6890,6 +6890,7 @@ dependencies = [ "sp-runtime", "sp-std", "spacewalk-primitives", + "staking", ] [[package]] diff --git a/pallets/fee/src/ext.rs b/pallets/fee/src/ext.rs index a5c6262c2..707b9905f 100644 --- a/pallets/fee/src/ext.rs +++ b/pallets/fee/src/ext.rs @@ -5,13 +5,13 @@ use mocktopus::macros::mockable; pub(crate) mod reward_distribution { use crate::DispatchError; use currency::Amount; - use reward_distribution::DistributeRewardsToPool; + use reward_distribution::DistributeRewards; pub fn distribute_rewards( reward: &Amount, ) -> Result, DispatchError> { let undistributed_balance = - T::DistributePool::distribute_rewards(reward.amount(), reward.currency())?; + T::RewardDistribution::distribute_rewards(reward.amount(), reward.currency())?; Ok(Amount::::new(undistributed_balance, reward.currency())) } } diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index c864d930c..e93db2bf1 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -111,7 +111,7 @@ pub mod pallet { >; /// Pooled rewards distribution Interface - type DistributePool: reward_distribution::DistributeRewardsToPool< + type RewardDistribution: reward_distribution::DistributeRewards< BalanceOf, CurrencyId, >; diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index 19601ed64..d5cd315cb 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -269,7 +269,7 @@ impl Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 2f473bcc6..d855dd46a 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -304,7 +304,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } parameter_types! { diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index a1eb9bd27..f0005de41 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -267,7 +267,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } impl oracle::Config for Test { diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index 0fb257f75..47fa80bea 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -335,7 +335,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } parameter_types! { diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index 958b37b59..014006674 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -332,7 +332,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } parameter_types! { diff --git a/pallets/reward-distribution/Cargo.toml b/pallets/reward-distribution/Cargo.toml index 1f859ad11..8656fb2a9 100644 --- a/pallets/reward-distribution/Cargo.toml +++ b/pallets/reward-distribution/Cargo.toml @@ -29,6 +29,7 @@ security = { path = "../security", default-features = false } currency = { path = "../currency", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } oracle = { path = "../oracle", default-features = false } +staking = {path = "../staking", default-features = false} [dev-dependencies] mocktopus = "0.8.0" @@ -54,6 +55,7 @@ std = [ "oracle/std", "primitives/std", "security/std", + "staking/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/reward-distribution/src/default_weights.rs b/pallets/reward-distribution/src/default_weights.rs index c0213b4d2..f9ee0b027 100644 --- a/pallets/reward-distribution/src/default_weights.rs +++ b/pallets/reward-distribution/src/default_weights.rs @@ -32,6 +32,7 @@ use core::marker::PhantomData; pub trait WeightInfo { fn set_reward_per_block() -> Weight; fn on_initialize() -> Weight; + fn collect_reward() -> Weight; } /// Weights for reward_distribution using the Substrate node and recommended hardware. @@ -58,4 +59,13 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(2)) } + + fn collect_reward() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } } diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 7efeab3a5..475f0d168 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -20,20 +20,56 @@ pub(crate) mod security { #[cfg_attr(test, mockable)] pub(crate) mod pooled_rewards { + use crate::{types::BalanceOf, DefaultVaultId}; + use currency::CurrencyId; use frame_support::pallet_prelude::DispatchResult; use pooled_rewards::RewardsApi; use sp_runtime::DispatchError; use sp_std::vec::Vec; pub fn get_total_stake_all_pools( - ) -> Result, DispatchError> { + ) -> Result, BalanceOf)>, DispatchError> { T::VaultRewards::get_total_stake_all_pools() } pub fn distribute_reward( - pool_id: &T::Currency, - reward_currency: &T::Currency, - amount: T::Balance, + pool_id: &CurrencyId, + reward_currency: &CurrencyId, + amount: BalanceOf, ) -> DispatchResult { T::VaultRewards::distribute_reward(pool_id, reward_currency.clone(), amount) } + + pub fn withdraw_reward( + pool_id: &CurrencyId, + vault_id: &DefaultVaultId, + reward_currency_id: CurrencyId, + ) -> Result, DispatchError> { + T::VaultRewards::withdraw_reward(pool_id, vault_id, reward_currency_id) + } +} + +#[cfg_attr(test, mockable)] +pub(crate) mod staking { + use currency::CurrencyId; + use frame_support::dispatch::DispatchError; + use staking::Staking; + + use crate::{types::BalanceOf, DefaultVaultId}; + + pub fn distribute_reward( + vault_id: &DefaultVaultId, + amount: BalanceOf, + currency_id: CurrencyId, + ) -> Result, DispatchError> { + T::VaultStaking::distribute_reward(vault_id, amount, currency_id) + } + + pub fn withdraw_reward( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + index: Option, + currency_id: CurrencyId, + ) -> Result, DispatchError> { + T::VaultStaking::withdraw_reward(vault_id, nominator_id, index, currency_id) + } } diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 8c1c4e917..8210b199d 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -8,18 +8,21 @@ mod default_weights; use crate::types::{BalanceOf, DefaultVaultId}; -use codec::{FullCodec, FullEncode, MaxEncodedLen}; +use codec::{FullCodec, MaxEncodedLen}; + +use core::fmt::Debug; +use currency::{Amount, CurrencyId}; pub use default_weights::{SubstrateWeight, WeightInfo}; use frame_support::{ - dispatch::DispatchResult, pallet_prelude::DispatchError, traits::Get, transactional, BoundedVec, + dispatch::DispatchResult, + pallet_prelude::DispatchError, + traits::{Currency, Get}, + transactional, BoundedVec, }; use oracle::OracleApi; -use sp_arithmetic::Perquintill; -use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedAdd, One, Zero}, - FixedPointOperand, -}; -use sp_std::{fmt::Debug, vec::Vec}; +use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; +use sp_runtime::traits::{CheckedAdd, One, Zero}; +use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -45,7 +48,9 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + security::Config { + pub trait Config: + frame_system::Config + security::Config + currency::Config> + { /// The overarching event type. type RuntimeEvent: From> + Into<::RuntimeEvent> @@ -54,17 +59,6 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - /// The currency trait. - type Currency: Parameter - + Member - + Copy - + Clone - + Decode - + FullEncode - + TypeInfo - + MaxEncodedLen - + Debug; - //balance type Balance: AtLeast32BitUnsigned + TypeInfo @@ -79,18 +73,28 @@ pub mod pallet { + From; type VaultRewards: pooled_rewards::RewardsApi< - Self::Currency, + CurrencyId, DefaultVaultId, BalanceOf, - Self::Currency, + CurrencyId, + >; + + /// Vault staking pool. + type VaultStaking: staking::Staking< + DefaultVaultId, + Self::AccountId, + Self::Index, + BalanceOf, + CurrencyId, >; type MaxCurrencies: Get; - //type OracleApi: ToUsdApi; - type OracleApi: oracle::OracleApi; + type FeeAccountId: Get; + + type OracleApi: oracle::OracleApi, CurrencyId>; - type GetNativeCurrencyId: Get; + type Balances: Currency>; /// Defines the interval (in number of blocks) at which the reward per block decays. #[pallet::constant] @@ -112,6 +116,8 @@ pub mod pallet { pub enum Error { //Overflow Overflow, + //if origin tries to withdraw with 0 rewards + NoRewardsForAccount, } #[pallet::hooks] @@ -136,10 +142,7 @@ pub mod pallet { #[pallet::getter(fn rewards_percentage)] pub(super) type RewardsPercentage = StorageValue< _, - BoundedVec< - (::Currency, Perquintill), - ::MaxCurrencies, - >, + BoundedVec<(CurrencyId, Perquintill), ::MaxCurrencies>, OptionQuery, >; @@ -162,23 +165,45 @@ pub mod pallet { Ok(()) } - // #[pallet::call_index(1)] - // #[pallet::weight(::WeightInfo::set_reward_per_block())] - // #[transactional] - // pub fn collect_rewards( - // origin: OriginFor, - // collateral_asset: T::Currency, - // ) -> DispatchResult { - // let nominator_id = ensure_signed(origin)?; - // } - - //input (account_id, collateral_asset_id, Optional(reward_currency_id)) - //Expectation: transfer the value of rewards that an account_id has - // - //get from new fx the list of vaults per account id - //execute withdraw on pool rewards for each vault_id (vault_id.collateral, - // vault_id, reward_currency_id) - //transfer rewards from stake + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::collect_reward())] + #[transactional] + pub fn collect_reward( + origin: OriginFor, + vault_id: DefaultVaultId, + reward_currency_id: CurrencyId, + index: Option, + ) -> DispatchResult { + //distribute reward from reward pool to staking pallet store + let caller = ensure_signed(origin)?; + let reward = ext::pooled_rewards::withdraw_reward::( + &vault_id.collateral_currency(), + &vault_id, + reward_currency_id, + )?; + ext::staking::distribute_reward::(&vault_id, reward, reward_currency_id)?; + + //withdraw the reward for specific nominator + let rewards = + ext::staking::withdraw_reward::(&vault_id, &caller, index, reward_currency_id)?; + + if rewards == (BalanceOf::::zero()) { + return Err(Error::::NoRewardsForAccount.into()) + } + + //either transfer from fee account if the reward is + //not the native currency, or just mint it if it is + + let native_currency_id = T::GetNativeCurrencyId::get(); + if reward_currency_id == native_currency_id { + T::Balances::deposit_creating(&caller, rewards); + return Ok(()) + } else { + let amount: currency::Amount = Amount::new(rewards, reward_currency_id); + amount.transfer(&T::FeeAccountId::get(), &caller)?; + return Ok(()) + } + } } } @@ -230,7 +255,7 @@ impl Pallet { //distribute the reward accoridigly fn distribute_rewards( reward_amount: BalanceOf, - reward_currency: T::Currency, + reward_currency: CurrencyId, ) -> Result, DispatchError> { //calculate the total stake across all collateral pools in USD let total_stakes = ext::pooled_rewards::get_total_stake_all_pools::()?; @@ -241,7 +266,7 @@ impl Pallet { total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } //distribute the rewards to each collateral pool - let mut percentages_vec = Vec::<(T::Currency, Perquintill)>::new(); + let mut percentages_vec = Vec::<(CurrencyId, Perquintill)>::new(); let mut error_reward_accum = BalanceOf::::zero(); for (currency_id, stake) in total_stakes.into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; @@ -272,17 +297,17 @@ impl Pallet { } } //Distribute Rewards interface -pub trait DistributeRewardsToPool { +pub trait DistributeRewards { fn distribute_rewards( amount: Balance, currency_id: CurrencyId, ) -> Result; } -impl DistributeRewardsToPool, T::Currency> for Pallet { +impl DistributeRewards, CurrencyId> for Pallet { fn distribute_rewards( amount: BalanceOf, - currency_id: T::Currency, + currency_id: CurrencyId, ) -> Result, DispatchError> { Pallet::::distribute_rewards(amount, currency_id) } diff --git a/pallets/reward-distribution/src/types.rs b/pallets/reward-distribution/src/types.rs index 0b128f640..267b1a6cb 100644 --- a/pallets/reward-distribution/src/types.rs +++ b/pallets/reward-distribution/src/types.rs @@ -1,6 +1,7 @@ use crate::Config; -use primitives::{CurrencyId, VaultId}; +use currency::CurrencyId; +use primitives::VaultId; pub(crate) type BalanceOf = ::Balance; -pub(crate) type DefaultVaultId = VaultId<::AccountId, CurrencyId>; +pub(crate) type DefaultVaultId = VaultId<::AccountId, CurrencyId>; diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index 354b2b3ab..ccf95afd0 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -250,7 +250,7 @@ impl fee::Config for Test { type VaultStaking = Staking; type OnSweep = (); type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } parameter_types! { diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index f87cbb530..8b3ff726f 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -549,7 +549,7 @@ impl fee::Config for Runtime { type VaultStaking = VaultStaking; type OnSweep = currency::SweepFunds; type MaxExpectedValue = MaxExpectedValue; - type DistributePool = RewardDistribution; + type RewardDistribution = RewardDistribution; } impl nomination::Config for Runtime { From b5f139158a7e5691868d40e4f8c934e7916e19a7 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 17 Oct 2023 12:05:59 -0300 Subject: [PATCH 26/52] wip --- Cargo.lock | 3 + pallets/reward-distribution/Cargo.toml | 9 +++ pallets/reward-distribution/src/ext.rs | 1 + pallets/reward-distribution/src/mock.rs | 94 +++++++++++++++++++++--- pallets/reward-distribution/src/tests.rs | 7 +- 5 files changed, 103 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e3e46a51..c26de5207 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6878,6 +6878,9 @@ dependencies = [ "log", "mocktopus", "oracle", + "orml-currencies", + "orml-tokens", + "orml-traits", "pallet-balances", "parity-scale-codec", "pooled-rewards", diff --git a/pallets/reward-distribution/Cargo.toml b/pallets/reward-distribution/Cargo.toml index 8656fb2a9..3df598cb5 100644 --- a/pallets/reward-distribution/Cargo.toml +++ b/pallets/reward-distribution/Cargo.toml @@ -24,6 +24,12 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40", default-features = false } +#orml dependencies +orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false } +orml-tokens= { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false } +orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.40", default-features = false } + + primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } security = { path = "../security", default-features = false } currency = { path = "../currency", default-features = false } @@ -54,6 +60,9 @@ std = [ "pooled-rewards/std", "oracle/std", "primitives/std", + "orml-currencies/std", + "orml-tokens/std", + "orml-traits/std", "security/std", "staking/std", ] diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 475f0d168..61224cba8 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -26,6 +26,7 @@ pub(crate) mod pooled_rewards { use pooled_rewards::RewardsApi; use sp_runtime::DispatchError; use sp_std::vec::Vec; + pub fn get_total_stake_all_pools( ) -> Result, BalanceOf)>, DispatchError> { T::VaultRewards::get_total_stake_all_pools() diff --git a/pallets/reward-distribution/src/mock.rs b/pallets/reward-distribution/src/mock.rs index db0189914..0e748d8af 100644 --- a/pallets/reward-distribution/src/mock.rs +++ b/pallets/reward-distribution/src/mock.rs @@ -4,21 +4,22 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64, Everything}, }; -use primitives::CurrencyId::XCM; -use sp_arithmetic::FixedI128; +use orml_currencies::BasicCurrencyAdapter; +use primitives::{Balance, CurrencyId, CurrencyId::XCM, VaultId}; +use sp_arithmetic::{FixedI128, FixedU128}; use sp_core::H256; use sp_runtime::{ generic::Header as GenericHeader, traits::{BlakeTwo256, IdentityLookup}, DispatchError, Perquintill, }; - -use primitives::{Balance, CurrencyId, VaultId}; type Header = GenericHeader; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +use orml_traits::parameter_type_with_key; +use sp_arithmetic::traits::Zero; -pub use currency::testing_constants::DEFAULT_NATIVE_CURRENCY; +pub use currency::testing_constants::{DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY}; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -28,11 +29,17 @@ frame_support::construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Storage, Config, Event}, + + //Tokens and Balances Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Security: security::{Pallet, Call, Storage, Event}, + Tokens: orml_tokens::{Pallet, Storage, Config, Event}, + Currencies: orml_currencies::{Pallet, Call}, RewardDistribution: reward_distribution::{Pallet, Call, Storage, Event}, - Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + + //Operational + Security: security::{Pallet, Call, Storage, Event}, + } ); @@ -40,6 +47,10 @@ pub type BlockNumber = u64; pub type Index = u64; pub type AccountId = u64; pub type SignedFixedPoint = FixedI128; +pub type UnsignedInner = u128; +pub type RawAmount = i128; +pub type SignedInner = i128; +pub type UnsignedFixedPoint = FixedU128; parameter_types! { pub const BlockHashCount: u64 = 250; @@ -104,6 +115,7 @@ parameter_types! { parameter_types! { pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; + pub const GetRelayChainCurrencyId: CurrencyId = DEFAULT_COLLATERAL_CURRENCY; pub const MaxCurrencies: u32 = 10; } @@ -120,6 +132,72 @@ impl pooled_rewards::Config for Test { type MaxRewardCurrencies = MaxRewardCurrencies; } +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Zero::zero() + }; +} + +pub struct CurrencyHooks(sp_std::marker::PhantomData); +impl + orml_traits::currency::MutationHooks for CurrencyHooks +{ + type OnDust = (); + type OnSlash = (); + type PreDeposit = (); + type PostDeposit = (); + type PreTransfer = (); + type PostTransfer = (); + type OnNewTokenAccount = (); + type OnKilledTokenAccount = (); +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = RawAmount; + type CurrencyId = CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type CurrencyHooks = CurrencyHooks; + type MaxLocks = MaxLocks; + type MaxReserves = ConstU32<0>; + type ReserveIdentifier = (); + type DustRemovalWhitelist = Everything; +} + +pub type Amount = i128; + +impl orml_currencies::Config for Test { + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type GetNativeCurrencyId = GetNativeCurrencyId; + type WeightInfo = (); +} + +pub struct CurrencyConvert; +impl currency::CurrencyConversion, CurrencyId> for CurrencyConvert { + fn convert( + _amount: ¤cy::Amount, + _to: CurrencyId, + ) -> Result, sp_runtime::DispatchError> { + unimplemented!() + } +} + +impl currency::Config for Test { + type UnsignedFixedPoint = UnsignedFixedPoint; + type SignedInner = SignedInner; + type SignedFixedPoint = SignedFixedPoint; + type Balance = Balance; + type GetRelayChainCurrencyId = GetRelayChainCurrencyId; + + type AssetConversion = primitives::AssetConversion; + type BalanceConversion = primitives::BalanceConversion; + type CurrencyConversion = CurrencyConvert; + type AmountCompatibility = primitives::StellarCompatibility; +} + pub struct OracleApiMock {} impl oracle::OracleApi for OracleApiMock { fn currency_to_usd( @@ -140,12 +218,10 @@ impl oracle::OracleApi for OracleApiMock { impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = crate::default_weights::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; } diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index d91c5cec0..c70a58690 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -7,7 +7,7 @@ pub use currency::testing_constants::DEFAULT_COLLATERAL_CURRENCY; use primitives::CurrencyId::XCM; fn build_total_stakes( -) -> Vec<(::Currency, ::Balance)> { +) -> Vec<(::CurrencyId, ::Balance)> { //total in usd 215000 vec![(DEFAULT_COLLATERAL_CURRENCY, 1000), (XCM(1), 3000), (XCM(2), 5000), (XCM(3), 500)] } @@ -89,7 +89,10 @@ fn on_initialize_hook_distribution_works() { expected_stake_per_pool.next().expect("More calls than expected"); assert_eq!(pool_id, &expected_pool_id); assert_eq!(amount, expected_stake_per_this_pool); - assert_eq!(reward_currency, &::GetNativeCurrencyId::get()); + assert_eq!( + reward_currency, + &::GetNativeCurrencyId::get() + ); MockResult::Return(Ok(())) }, ); From 000d0c01521e3b4bc62475298fa13f78f4c47d46 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 17 Oct 2023 13:42:51 -0300 Subject: [PATCH 27/52] modified mock configs due to changes in reward-distribution, removed store of calculated percentages in pallet --- pallets/fee/src/mock.rs | 5 +++-- pallets/issue/src/mock.rs | 5 +++-- pallets/nomination/src/mock.rs | 5 +++-- pallets/redeem/src/mock.rs | 5 +++-- pallets/replace/src/mock.rs | 5 +++-- pallets/reward-distribution/src/lib.rs | 30 +++++++------------------ pallets/reward-distribution/src/mock.rs | 19 +++++++++++++++- pallets/vault-registry/src/mock.rs | 5 +++-- testchain/runtime/src/lib.rs | 19 +++------------- 9 files changed, 47 insertions(+), 51 deletions(-) diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index d5cd315cb..6129151e5 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -248,14 +248,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } impl Config for Test { diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index d855dd46a..7ce0f42b4 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -342,14 +342,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } parameter_types! { diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index f0005de41..e260abef1 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -319,14 +319,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } impl Config for Test { diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index 47fa80bea..8a4e5464a 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -360,14 +360,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } impl Config for Test { diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index 014006674..87d57a045 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -357,14 +357,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } impl Config for Test { diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 8210b199d..a787447ea 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -17,12 +17,11 @@ use frame_support::{ dispatch::DispatchResult, pallet_prelude::DispatchError, traits::{Currency, Get}, - transactional, BoundedVec, + transactional, PalletId, }; use oracle::OracleApi; use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; -use sp_runtime::traits::{CheckedAdd, One, Zero}; -use sp_std::vec::Vec; +use sp_runtime::traits::{AccountIdConversion, CheckedAdd, One, Zero}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -90,7 +89,7 @@ pub mod pallet { type MaxCurrencies: Get; - type FeeAccountId: Get; + type FeePalletId: Get; type OracleApi: oracle::OracleApi, CurrencyId>; @@ -138,14 +137,6 @@ pub mod pallet { #[pallet::getter(fn rewards_adapted_at)] pub(super) type RewardsAdaptedAt = StorageValue<_, BlockNumberFor, OptionQuery>; - #[pallet::storage] - #[pallet::getter(fn rewards_percentage)] - pub(super) type RewardsPercentage = StorageValue< - _, - BoundedVec<(CurrencyId, Perquintill), ::MaxCurrencies>, - OptionQuery, - >; - #[pallet::call] impl Pallet { /// Sets the reward per block. @@ -200,7 +191,7 @@ pub mod pallet { return Ok(()) } else { let amount: currency::Amount = Amount::new(rewards, reward_currency_id); - amount.transfer(&T::FeeAccountId::get(), &caller)?; + amount.transfer(&Self::fee_pool_account_id(), &caller)?; return Ok(()) } } @@ -266,7 +257,6 @@ impl Pallet { total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } //distribute the rewards to each collateral pool - let mut percentages_vec = Vec::<(CurrencyId, Perquintill)>::new(); let mut error_reward_accum = BalanceOf::::zero(); for (currency_id, stake) in total_stakes.into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; @@ -283,18 +273,14 @@ impl Pallet { error_reward_accum = error_reward_accum.checked_add(&reward_for_pool).ok_or(Error::::Overflow)?; } - - percentages_vec.push((currency_id, percentage)) } - //we store the calculated percentages which are good as long as - //prices are unchanged - let bounded_vec = BoundedVec::try_from(percentages_vec) - .expect("should not be longer than max currencies"); - RewardsPercentage::::set(Some(bounded_vec)); - Ok(error_reward_accum) } + + pub fn fee_pool_account_id() -> T::AccountId { + ::FeePalletId::get().into_account_truncating() + } } //Distribute Rewards interface pub trait DistributeRewards { diff --git a/pallets/reward-distribution/src/mock.rs b/pallets/reward-distribution/src/mock.rs index 0e748d8af..e8aa1d043 100644 --- a/pallets/reward-distribution/src/mock.rs +++ b/pallets/reward-distribution/src/mock.rs @@ -3,6 +3,7 @@ use crate::Config; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64, Everything}, + PalletId, }; use orml_currencies::BasicCurrencyAdapter; use primitives::{Balance, CurrencyId, CurrencyId::XCM, VaultId}; @@ -36,6 +37,7 @@ frame_support::construct_runtime!( Currencies: orml_currencies::{Pallet, Call}, RewardDistribution: reward_distribution::{Pallet, Call, Storage, Event}, Rewards: pooled_rewards::{Pallet, Call, Storage, Event}, + Staking: staking::{Pallet, Storage, Event}, //Operational Security: security::{Pallet, Call, Storage, Event}, @@ -47,7 +49,6 @@ pub type BlockNumber = u64; pub type Index = u64; pub type AccountId = u64; pub type SignedFixedPoint = FixedI128; -pub type UnsignedInner = u128; pub type RawAmount = i128; pub type SignedInner = i128; pub type UnsignedFixedPoint = FixedU128; @@ -215,6 +216,19 @@ impl oracle::OracleApi for OracleApiMock { Ok(amount_in_usd) } } + +impl staking::Config for Test { + type RuntimeEvent = RuntimeEvent; + type SignedFixedPoint = SignedFixedPoint; + type SignedInner = SignedInner; + type CurrencyId = CurrencyId; + type GetNativeCurrencyId = GetNativeCurrencyId; +} + +parameter_types! { + pub const FeePalletId: PalletId = PalletId(*b"mod/fees"); +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = crate::default_weights::SubstrateWeight; @@ -224,6 +238,9 @@ impl Config for Test { type VaultRewards = Rewards; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } pub struct ExtBuilder; diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index ccf95afd0..5efd15e73 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -275,14 +275,15 @@ impl oracle::OracleApi for OracleApiMock { impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU64<100>; type DecayRate = DecayRate; type VaultRewards = Rewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = OracleApiMock; + type Balances = Balances; + type VaultStaking = Staking; + type FeePalletId = FeePalletId; } parameter_types! { diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index 8b3ff726f..09d227804 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -569,31 +569,18 @@ parameter_types! { pub const MaxCurrencies: u32 = 10; } -// pub struct OracleApi {} -// impl reward_distribution::ToUsdApi for OracleApi { -// fn currency_to_usd( -// _amount: &Balance, -// currency_id: &CurrencyId, -// ) -> Result { -// let _native_currency = GetNativeCurrencyId::get(); -// match currency_id { -// _native_currency => return Ok(100), -// //_ => unimplemented!("unimplemented mock conversion for currency"), -// } -// } -// } - impl reward_distribution::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = reward_distribution::SubstrateWeight; - type Currency = CurrencyId; type Balance = Balance; type DecayInterval = ConstU32<100>; type DecayRate = DecayRate; type VaultRewards = VaultRewards; - type GetNativeCurrencyId = GetNativeCurrencyId; type MaxCurrencies = MaxCurrencies; type OracleApi = Oracle; + type Balances = Balances; + type VaultStaking = VaultStaking; + type FeePalletId = FeePalletId; } parameter_types! { From 718345e8761c399b8b58baf84e1ae9e24d3174ad Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 17 Oct 2023 17:27:19 -0300 Subject: [PATCH 28/52] added benchmarks for collect extrinsic and on initialize function --- .../reward-distribution/src/benchmarking.rs | 84 +++++++++++- .../src/default_weights.rs | 128 +++++++++++++++--- pallets/reward-distribution/src/lib.rs | 2 - 3 files changed, 194 insertions(+), 20 deletions(-) diff --git a/pallets/reward-distribution/src/benchmarking.rs b/pallets/reward-distribution/src/benchmarking.rs index 5de9fed10..1ea4269e0 100644 --- a/pallets/reward-distribution/src/benchmarking.rs +++ b/pallets/reward-distribution/src/benchmarking.rs @@ -1,12 +1,17 @@ #[allow(unused)] use super::Pallet as RewardDistribution; + use super::*; +use crate::types::DefaultVaultId; +use currency::getters::get_relay_chain_currency_id as get_collateral_currency_id; +pub use currency::testing_constants::{DEFAULT_COLLATERAL_CURRENCY, DEFAULT_WRAPPED_CURRENCY}; use frame_benchmarking::{ - v2::{benchmarks, impl_benchmark_test_suite}, + v2::{account, benchmarks, impl_benchmark_test_suite}, vec, }; use frame_system::RawOrigin; - +use pooled_rewards::RewardsApi; +use staking::Staking; #[benchmarks] pub mod benchmarks { use super::*; @@ -21,6 +26,81 @@ pub mod benchmarks { assert_eq!(RewardDistribution::::reward_per_block(), Some(new_reward_per_block)); } + #[benchmark] + fn collect_reward() { + //initial values + let collateral_currency = get_collateral_currency_id::(); + let native_currency_id = T::GetNativeCurrencyId::get(); + let nominator = account("Alice", 0, 0); + let nominated_amount: u32 = 40000; + let reward_to_distribute: u32 = 100; + + //get the vault + let vault_id: DefaultVaultId = DefaultVaultId::::new( + account("Vault", 0, 0), + collateral_currency, + collateral_currency, + ); + + let _stake = + T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()).unwrap(); + let _reward_stake = T::VaultRewards::deposit_stake( + &collateral_currency, + &vault_id, + nominated_amount.into(), + ) + .unwrap(); + let _distributed = T::VaultRewards::distribute_reward( + &collateral_currency, + native_currency_id.clone(), + reward_to_distribute.clone().into(), + ) + .unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(nominator.clone()), vault_id, native_currency_id, None); + } + + #[benchmark] + fn on_initialize() { + //initial values + let collateral_currency = get_collateral_currency_id::(); + let _native_currency_id = T::GetNativeCurrencyId::get(); + let nominator = account("Alice", 0, 0); + let nominated_amount: u32 = 40000; + + //set reward per block + let new_reward_per_block: u64 = 5; + RewardDistribution::::set_reward_per_block( + RawOrigin::Root.into(), + new_reward_per_block.clone().into(), + ) + .unwrap(); + assert_eq!(RewardDistribution::::reward_per_block(), Some(new_reward_per_block.into())); + + //set the vault and nominate it + let vault_id: DefaultVaultId = DefaultVaultId::::new( + account("Vault", 0, 0), + collateral_currency, + collateral_currency, + ); + + let _stake = + T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()).unwrap(); + let _reward_stake = T::VaultRewards::deposit_stake( + &collateral_currency, + &vault_id, + nominated_amount.into(), + ) + .unwrap(); + + // `on_initialize` benchmark call + #[block] + { + RewardDistribution::::execute_on_init(1u32.into()); + } + } + impl_benchmark_test_suite!( RewardDistribution, crate::mock::ExtBuilder::build(), diff --git a/pallets/reward-distribution/src/default_weights.rs b/pallets/reward-distribution/src/default_weights.rs index f9ee0b027..d9299e372 100644 --- a/pallets/reward-distribution/src/default_weights.rs +++ b/pallets/reward-distribution/src/default_weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for reward_distribution //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-10, STEPS: `100`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-17, STEPS: `100`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Marcels-MBP`, CPU: `` +//! HOSTNAME: `pop-os`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -31,8 +31,8 @@ use core::marker::PhantomData; /// Weight functions needed for reward_distribution. pub trait WeightInfo { fn set_reward_per_block() -> Weight; - fn on_initialize() -> Weight; fn collect_reward() -> Weight; + fn on_initialize() -> Weight; } /// Weights for reward_distribution using the Substrate node and recommended hardware. @@ -46,26 +46,122 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) + // Minimum execution time: 11_898_000 picoseconds. + Weight::from_parts(12_110_000, 0) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: VaultRewards Stake (r:1 w:0) + /// Proof: VaultRewards Stake (max_values: None, max_size: Some(202), added: 2677, mode: MaxEncodedLen) + /// Storage: VaultRewards RewardPerToken (r:1 w:0) + /// Proof: VaultRewards RewardPerToken (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: VaultRewards RewardTally (r:1 w:1) + /// Proof: VaultRewards RewardTally (max_values: None, max_size: Some(264), added: 2739, mode: MaxEncodedLen) + /// Storage: VaultRewards TotalRewards (r:1 w:1) + /// Proof: VaultRewards TotalRewards (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: VaultStaking Nonce (r:1 w:0) + /// Proof Skipped: VaultStaking Nonce (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalCurrentStake (r:1 w:0) + /// Proof Skipped: VaultStaking TotalCurrentStake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking RewardPerToken (r:1 w:1) + /// Proof Skipped: VaultStaking RewardPerToken (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalRewards (r:1 w:1) + /// Proof Skipped: VaultStaking TotalRewards (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking Stake (r:1 w:1) + /// Proof Skipped: VaultStaking Stake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking SlashPerToken (r:1 w:0) + /// Proof Skipped: VaultStaking SlashPerToken (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking SlashTally (r:1 w:1) + /// Proof Skipped: VaultStaking SlashTally (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalStake (r:1 w:1) + /// Proof Skipped: VaultStaking TotalStake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking RewardTally (r:1 w:1) + /// Proof Skipped: VaultStaking RewardTally (max_values: None, max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn collect_reward() -> Weight { + // Proof Size summary in bytes: + // Measured: `1118` + // Estimated: `59384` + // Minimum execution time: 63_270_000 picoseconds. + Weight::from_parts(64_571_000, 59384) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: RewardDistribution RewardPerBlock (r:1 w:0) + /// Proof: RewardDistribution RewardPerBlock (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Security ParachainStatus (r:1 w:0) + /// Proof Skipped: Security ParachainStatus (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 1_000_000 picoseconds. - Weight::from_parts(2_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `232` + // Estimated: `3218` + // Minimum execution time: 16_761_000 picoseconds. + Weight::from_parts(16_934_000, 3218) + .saturating_add(T::DbWeight::get().reads(2_u64)) } +} - fn collect_reward() -> Weight { +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: RewardDistribution RewardsAdaptedAt (r:0 w:1) + /// Proof: RewardDistribution RewardsAdaptedAt (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: RewardDistribution RewardPerBlock (r:0 w:1) + /// Proof: RewardDistribution RewardPerBlock (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + fn set_reward_per_block() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) - .saturating_add(T::DbWeight::get().writes(2_u64)) + // Minimum execution time: 11_898_000 picoseconds. + Weight::from_parts(12_110_000, 0) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } -} + /// Storage: VaultRewards Stake (r:1 w:0) + /// Proof: VaultRewards Stake (max_values: None, max_size: Some(202), added: 2677, mode: MaxEncodedLen) + /// Storage: VaultRewards RewardPerToken (r:1 w:0) + /// Proof: VaultRewards RewardPerToken (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: VaultRewards RewardTally (r:1 w:1) + /// Proof: VaultRewards RewardTally (max_values: None, max_size: Some(264), added: 2739, mode: MaxEncodedLen) + /// Storage: VaultRewards TotalRewards (r:1 w:1) + /// Proof: VaultRewards TotalRewards (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: VaultStaking Nonce (r:1 w:0) + /// Proof Skipped: VaultStaking Nonce (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalCurrentStake (r:1 w:0) + /// Proof Skipped: VaultStaking TotalCurrentStake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking RewardPerToken (r:1 w:1) + /// Proof Skipped: VaultStaking RewardPerToken (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalRewards (r:1 w:1) + /// Proof Skipped: VaultStaking TotalRewards (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking Stake (r:1 w:1) + /// Proof Skipped: VaultStaking Stake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking SlashPerToken (r:1 w:0) + /// Proof Skipped: VaultStaking SlashPerToken (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking SlashTally (r:1 w:1) + /// Proof Skipped: VaultStaking SlashTally (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking TotalStake (r:1 w:1) + /// Proof Skipped: VaultStaking TotalStake (max_values: None, max_size: None, mode: Measured) + /// Storage: VaultStaking RewardTally (r:1 w:1) + /// Proof Skipped: VaultStaking RewardTally (max_values: None, max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn collect_reward() -> Weight { + // Proof Size summary in bytes: + // Measured: `1118` + // Estimated: `59384` + // Minimum execution time: 63_270_000 picoseconds. + Weight::from_parts(64_571_000, 59384) + .saturating_add(RocksDbWeight::get().reads(14_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } + /// Storage: RewardDistribution RewardPerBlock (r:1 w:0) + /// Proof: RewardDistribution RewardPerBlock (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Security ParachainStatus (r:1 w:0) + /// Proof Skipped: Security ParachainStatus (max_values: Some(1), max_size: None, mode: Measured) + fn on_initialize() -> Weight { + // Proof Size summary in bytes: + // Measured: `232` + // Estimated: `3218` + // Minimum execution time: 16_761_000 picoseconds. + Weight::from_parts(16_934_000, 3218) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } +} \ No newline at end of file diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index a787447ea..8bd2d3d88 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -184,7 +184,6 @@ pub mod pallet { //either transfer from fee account if the reward is //not the native currency, or just mint it if it is - let native_currency_id = T::GetNativeCurrencyId::get(); if reward_currency_id == native_currency_id { T::Balances::deposit_creating(&caller, rewards); @@ -262,7 +261,6 @@ impl Pallet { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); let reward_for_pool = percentage.mul_floor(reward_amount); - if ext::pooled_rewards::distribute_reward::( ¤cy_id, &reward_currency, From 90276fd6c564665f65cd90418c35ef3eac9ef870 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 10:19:09 -0300 Subject: [PATCH 29/52] removed reward distribution logic from fee pallet --- pallets/fee/src/benchmarking.rs | 7 ---- pallets/fee/src/lib.rs | 74 +++------------------------------ pallets/nomination/src/ext.rs | 12 ------ pallets/nomination/src/lib.rs | 5 ++- 4 files changed, 9 insertions(+), 89 deletions(-) diff --git a/pallets/fee/src/benchmarking.rs b/pallets/fee/src/benchmarking.rs index ec233df87..aa8bcd420 100644 --- a/pallets/fee/src/benchmarking.rs +++ b/pallets/fee/src/benchmarking.rs @@ -1,9 +1,6 @@ use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_system::RawOrigin; -use currency::getters::get_native_currency_id; -use primitives::VaultId; - #[cfg(test)] use crate::Pallet as Fee; @@ -18,10 +15,6 @@ fn get_fee() -> UnsignedFixedPoint { } benchmarks! { - withdraw_rewards { - let nominator: T::AccountId = account("recipient", 0, SEED); - let vault_id: VaultId<_, CurrencyId> = VaultId::new(nominator.clone(), get_native_currency_id::(), get_native_currency_id::()); - }: _(RawOrigin::Signed(nominator), vault_id, None) set_issue_fee { let caller: T::AccountId = account("caller", 0, SEED); diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index e93db2bf1..efa0493c6 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -234,35 +234,13 @@ pub mod pallet { // The pallet's dispatchable functions. #[pallet::call] impl Pallet { - /// Withdraw all rewards from the `origin` account in the `vault_id` staking pool. - /// - /// # Arguments - /// - /// * `origin` - signing account - #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::withdraw_rewards())] - #[transactional] - pub fn withdraw_rewards( - origin: OriginFor, - vault_id: DefaultVaultId, - index: Option, - ) -> DispatchResultWithPostInfo { - let nominator_id = ensure_signed(origin)?; - Self::withdraw_from_reward_pool::( - &vault_id, - &nominator_id, - index, - )?; - Ok(().into()) - } - /// Changes the issue fee percentage (only executable by the Root account) /// /// # Arguments /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(1)] + #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::set_issue_fee())] #[transactional] pub fn set_issue_fee( @@ -281,7 +259,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `griefing_collateral` - the new griefing collateral - #[pallet::call_index(2)] + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::set_issue_griefing_collateral())] #[transactional] pub fn set_issue_griefing_collateral( @@ -303,7 +281,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(3)] + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::set_redeem_fee())] #[transactional] pub fn set_redeem_fee( @@ -322,7 +300,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(4)] + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::set_premium_redeem_fee())] #[transactional] pub fn set_premium_redeem_fee( @@ -341,7 +319,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(5)] + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::set_punishment_fee())] #[transactional] pub fn set_punishment_fee( @@ -360,7 +338,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `griefing_collateral` - the new griefing collateral - #[pallet::call_index(6)] + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::set_replace_griefing_collateral())] #[transactional] pub fn set_replace_griefing_collateral( @@ -467,47 +445,7 @@ impl Pallet { amount.rounded_mul(>::get()) } - pub fn withdraw_all_vault_rewards(vault_id: &DefaultVaultId) -> DispatchResult { - Self::distribute_from_reward_pool::(vault_id)?; - Ok(()) - } - - // Private functions internal to this pallet - /// Withdraw rewards from a pool and transfer to `account_id`. - fn withdraw_from_reward_pool< - Rewards: pooled_rewards::RewardsApi, DefaultVaultId, BalanceOf, CurrencyId>, - Staking: staking::Staking, T::AccountId, T::Index, BalanceOf, CurrencyId>, - >( - vault_id: &DefaultVaultId, - nominator_id: &T::AccountId, - index: Option, - ) -> DispatchResult { - Self::distribute_from_reward_pool::(vault_id)?; - - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { - let rewards = Staking::withdraw_reward(vault_id, nominator_id, index, currency_id)?; - let amount = Amount::::new(rewards, currency_id); - amount.transfer(&Self::fee_pool_account_id(), nominator_id)?; - } - Ok(()) - } - fn distribute(reward: &Amount) -> Result, DispatchError> { ext::reward_distribution::distribute_rewards::(reward) } - - pub fn distribute_from_reward_pool< - Rewards: pooled_rewards::RewardsApi, DefaultVaultId, BalanceOf, CurrencyId>, - Staking: staking::Staking, T::AccountId, T::Index, BalanceOf, CurrencyId>, - >( - vault_id: &DefaultVaultId, - ) -> DispatchResult { - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { - let reward = - Rewards::withdraw_reward(&vault_id.collateral_currency(), &vault_id, currency_id)?; - Staking::distribute_reward(vault_id, reward, currency_id)?; - } - - Ok(()) - } } diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index 1f725dfc7..f623c717e 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -62,18 +62,6 @@ pub(crate) mod vault_registry { } } -#[cfg_attr(test, mockable)] -pub(crate) mod fee { - use crate::DefaultVaultId; - use frame_support::dispatch::DispatchResult; - - pub fn withdraw_all_vault_rewards( - vault_id: &DefaultVaultId, - ) -> DispatchResult { - >::withdraw_all_vault_rewards(vault_id) - } -} - #[cfg_attr(test, mockable)] pub(crate) mod staking { use crate::BalanceOf; diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index 4f9c97b01..3afef3e84 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -242,7 +242,8 @@ impl Pallet { } // withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - ext::fee::withdraw_all_vault_rewards::(vault_id)?; + //TODO do with new reward-pallet + // withdraw `amount` of stake from the vault staking pool ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; @@ -284,7 +285,7 @@ impl Pallet { ); // Withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - ext::fee::withdraw_all_vault_rewards::(vault_id)?; + //TODO do with new reward-pallet // Deposit `amount` of stake into the vault staking pool ext::staking::deposit_stake::(vault_id, nominator_id, amount.amount())?; From 1cdeaa801b874e545c9ff04fc66c8ccecc9b775b Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 11:52:21 -0300 Subject: [PATCH 30/52] moved withdraw_all_rewards_from_vault to new pallet, added logic to handle native fees --- pallets/fee/src/lib.rs | 1 + pallets/nomination/src/ext.rs | 11 ++++ pallets/nomination/src/lib.rs | 6 +-- pallets/reward-distribution/src/lib.rs | 70 ++++++++++++++++++++------ 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index efa0493c6..d64d31fe7 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -114,6 +114,7 @@ pub mod pallet { type RewardDistribution: reward_distribution::DistributeRewards< BalanceOf, CurrencyId, + DefaultVaultId, >; /// Vault staking pool. diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index f623c717e..5f0b58292 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -62,6 +62,17 @@ pub(crate) mod vault_registry { } } +#[cfg_attr(test, mockable)] +pub(crate) mod reward_distribution { + use frame_support::pallet_prelude::DispatchResult; + use reward_distribution::DistributeRewards; + use vault_registry::DefaultVaultId; + pub fn withdraw_all_rewards_from_vault( + vault_id: &DefaultVaultId, + ) -> DispatchResult { + T::RewardDistribution::withdraw_all_rewards_from_vault(vault_id.clone()) + } +} #[cfg_attr(test, mockable)] pub(crate) mod staking { use crate::BalanceOf; diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index 3afef3e84..b25f8f744 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -242,8 +242,7 @@ impl Pallet { } // withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - //TODO do with new reward-pallet - + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; // withdraw `amount` of stake from the vault staking pool ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; @@ -285,8 +284,7 @@ impl Pallet { ); // Withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - //TODO do with new reward-pallet - + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; // Deposit `amount` of stake into the vault staking pool ext::staking::deposit_stake::(vault_id, nominator_id, amount.amount())?; amount.transfer(nominator_id, &vault_id.account_id)?; diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 8bd2d3d88..966ce2faf 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -9,7 +9,6 @@ mod default_weights; use crate::types::{BalanceOf, DefaultVaultId}; use codec::{FullCodec, MaxEncodedLen}; - use core::fmt::Debug; use currency::{Amount, CurrencyId}; pub use default_weights::{SubstrateWeight, WeightInfo}; @@ -21,7 +20,7 @@ use frame_support::{ }; use oracle::OracleApi; use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; -use sp_runtime::traits::{AccountIdConversion, CheckedAdd, One, Zero}; +use sp_runtime::traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -115,6 +114,8 @@ pub mod pallet { pub enum Error { //Overflow Overflow, + //underflow + Underflow, //if origin tries to withdraw with 0 rewards NoRewardsForAccount, } @@ -182,22 +183,44 @@ pub mod pallet { return Err(Error::::NoRewardsForAccount.into()) } - //either transfer from fee account if the reward is - //not the native currency, or just mint it if it is - let native_currency_id = T::GetNativeCurrencyId::get(); - if reward_currency_id == native_currency_id { - T::Balances::deposit_creating(&caller, rewards); + //transfer rewards + Self::transfer_reward(reward_currency_id, reward, caller) + } + } +} + +impl Pallet { + fn transfer_reward( + reward_currency_id: CurrencyId, + reward: BalanceOf, + beneficiary: T::AccountId, + ) -> DispatchResult { + let native_currency_id = T::GetNativeCurrencyId::get(); + if reward_currency_id == native_currency_id { + let available_native_funds = T::Balances::total_balance(&Self::fee_pool_account_id()); + + if available_native_funds < reward { + //if available native is less than reward we transfer first from fee account, + //and mint the rest + let remaining = + reward.checked_sub(&available_native_funds).ok_or(Error::::Underflow)?; + let amount: currency::Amount = + Amount::new(available_native_funds, reward_currency_id); + amount.transfer(&Self::fee_pool_account_id(), &beneficiary)?; + T::Balances::deposit_creating(&beneficiary, remaining); return Ok(()) } else { - let amount: currency::Amount = Amount::new(rewards, reward_currency_id); - amount.transfer(&Self::fee_pool_account_id(), &caller)?; - return Ok(()) + let amount: currency::Amount = Amount::new(reward, reward_currency_id); + return amount.transfer(&Self::fee_pool_account_id(), &beneficiary) } + } else { + //we need no checking of available funds, since fee will ALWAYS have enough collected + //fees + let amount: currency::Amount = Amount::new(reward, reward_currency_id); + amount.transfer(&Self::fee_pool_account_id(), &beneficiary) } } -} -impl Pallet { pub fn execute_on_init(height: T::BlockNumber) { //get reward per block let reward_per_block = match RewardPerBlock::::get() { @@ -276,23 +299,42 @@ impl Pallet { Ok(error_reward_accum) } + //TODO loop through all currencies not just these two!! + fn withdraw_all_rewards_from_vault(vault_id: DefaultVaultId) -> DispatchResult { + for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { + let reward = ext::pooled_rewards::withdraw_reward::( + &vault_id.collateral_currency(), + &vault_id, + currency_id, + )?; + ext::staking::distribute_reward::(&vault_id, reward, currency_id)?; + } + Ok(()) + } + pub fn fee_pool_account_id() -> T::AccountId { ::FeePalletId::get().into_account_truncating() } } //Distribute Rewards interface -pub trait DistributeRewards { +pub trait DistributeRewards { fn distribute_rewards( amount: Balance, currency_id: CurrencyId, ) -> Result; + + fn withdraw_all_rewards_from_vault(vault_id: VaultId) -> DispatchResult; } -impl DistributeRewards, CurrencyId> for Pallet { +impl DistributeRewards, CurrencyId, DefaultVaultId> for Pallet { fn distribute_rewards( amount: BalanceOf, currency_id: CurrencyId, ) -> Result, DispatchError> { Pallet::::distribute_rewards(amount, currency_id) } + + fn withdraw_all_rewards_from_vault(vault_id: DefaultVaultId) -> DispatchResult { + Pallet::::withdraw_all_rewards_from_vault(vault_id) + } } From 9c96d544b6a479aefcdcf45f9fae6f3c2467af7e Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 14:08:43 -0300 Subject: [PATCH 31/52] added new store to hold all reward currencies in stake pallet, modified withdraw all to use all currencies --- pallets/reward-distribution/src/ext.rs | 5 +++ pallets/reward-distribution/src/lib.rs | 4 +- pallets/staking/src/lib.rs | 56 ++++++++++++++++++++++---- pallets/vault-registry/src/ext.rs | 8 +++- pallets/vault-registry/src/lib.rs | 5 ++- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 61224cba8..a59477159 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -73,4 +73,9 @@ pub(crate) mod staking { ) -> Result, DispatchError> { T::VaultStaking::withdraw_reward(vault_id, nominator_id, index, currency_id) } + + pub fn get_all_reward_currencies() -> Result>, DispatchError> + { + T::VaultStaking::get_all_reward_currencies() + } } diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 966ce2faf..60f928d99 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -301,7 +301,9 @@ impl Pallet { //TODO loop through all currencies not just these two!! fn withdraw_all_rewards_from_vault(vault_id: DefaultVaultId) -> DispatchResult { - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { + let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + all_reward_currencies.push(T::GetNativeCurrencyId::get()); + for currency_id in all_reward_currencies { let reward = ext::pooled_rewards::withdraw_reward::( &vault_id.collateral_currency(), &vault_id, diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index b404770f4..ed030b82f 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -81,13 +81,12 @@ pub type DefaultVaultId = VaultId<::AccountId, ::CurrencyId>; pub type DefaultVaultCurrencyPair = VaultCurrencyPair<::CurrencyId>; pub type NominatorId = ::AccountId; - #[frame_support::pallet] pub mod pallet { - use frame_support::pallet_prelude::*; - use super::*; + use frame_support::{pallet_prelude::*, BoundedBTreeSet}; + /// ## Configuration /// The pallet's configuration trait. #[pallet::config] @@ -111,6 +110,9 @@ pub mod pallet { /// The currency ID type. type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord; + + // + type MaxRewardCurrencies: Get; } // The pallet's events @@ -156,6 +158,8 @@ pub mod pallet { InsufficientFunds, /// Cannot slash zero total stake. SlashZeroTotalStake, + /// Max rewards currencies threshold + MaxRewardCurrencies, } #[pallet::hooks] @@ -269,6 +273,11 @@ pub mod pallet { pub type Nonce = StorageMap<_, Blake2_128Concat, DefaultVaultId, T::Index, ValueQuery>; + // store with all the reward currencies in use + #[pallet::storage] + pub type RewardCurrencies = + StorageValue<_, BoundedBTreeSet, ValueQuery>; + #[pallet::pallet] #[pallet::without_storage_info] // no MaxEncodedLen for ::Index pub struct Pallet(_); @@ -399,8 +408,9 @@ impl Pallet { .ok_or(ArithmeticError::Overflow)?; Ok::<_, DispatchError>(()) })?; - - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { + let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + all_reward_currencies.push(T::GetNativeCurrencyId::get()); + for currency_id in all_reward_currencies { >::mutate( currency_id, (nonce, vault_id, nominator_id), @@ -478,7 +488,9 @@ impl Pallet { // A slash means reward per token is no longer representative of the rewards // since `amount * reward_per_token` will be lost from the system. As such, // replenish rewards by the amount of reward lost with this slash - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { + let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + all_reward_currencies.push(T::GetNativeCurrencyId::get()); + for currency_id in all_reward_currencies { Self::increase_rewards( nonce, currency_id, @@ -668,7 +680,9 @@ impl Pallet { Ok::<_, DispatchError>(()) })?; - for currency_id in [vault_id.wrapped_currency(), T::GetNativeCurrencyId::get()] { + let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + all_reward_currencies.push(T::GetNativeCurrencyId::get()); + for currency_id in all_reward_currencies { >::mutate( currency_id, (nonce, vault_id, nominator_id), @@ -777,6 +791,20 @@ impl Pallet { }); Ok(()) } + + pub fn add_reward_currency(currency_id: T::CurrencyId) -> DispatchResult { + RewardCurrencies::::try_mutate(|reward_currencies| { + reward_currencies + .try_insert(currency_id) + .map_err(|_| Error::::MaxRewardCurrencies) + })?; + Ok(()) + } + + pub fn get_all_reward_currencies() -> Result, DispatchError> { + let values = RewardCurrencies::::get().into_iter().collect::>(); + Ok(values) + } } pub trait Staking { @@ -834,6 +862,12 @@ pub trait Staking { /// Force refund the entire nomination to `vault_id`. fn force_refund(vault_id: &VaultId) -> Result; + + // set a new reward currency + + fn add_reward_currency(currency: CurrencyId) -> Result<(), DispatchError>; + + fn get_all_reward_currencies() -> Result, DispatchError>; } impl Staking, T::AccountId, T::Index, Balance, T::CurrencyId> @@ -936,6 +970,14 @@ where .try_into() .map_err(|_| Error::::TryIntoIntError.into()) } + + fn add_reward_currency(currency: T::CurrencyId) -> Result<(), DispatchError> { + Pallet::::add_reward_currency(currency) + } + + fn get_all_reward_currencies() -> Result, DispatchError> { + Pallet::::get_all_reward_currencies() + } } pub mod migration { diff --git a/pallets/vault-registry/src/ext.rs b/pallets/vault-registry/src/ext.rs index fdae58bc6..34467e6aa 100644 --- a/pallets/vault-registry/src/ext.rs +++ b/pallets/vault-registry/src/ext.rs @@ -31,9 +31,9 @@ pub(crate) mod security { #[cfg_attr(test, mockable)] pub(crate) mod staking { - use frame_support::dispatch::{DispatchError, DispatchResult}; - + use crate::types::CurrencyId; use currency::Amount; + use frame_support::dispatch::{DispatchError, DispatchResult}; use staking::Staking; use crate::{types::BalanceOf, DefaultVaultId}; @@ -80,6 +80,10 @@ pub(crate) mod staking { let vault_total_stake = T::VaultStaking::total_stake(vault_id)?; Ok(Amount::::new(vault_total_stake, vault_id.collateral_currency())) } + + pub fn add_reward_currency(currency: CurrencyId) -> DispatchResult { + T::VaultStaking::add_reward_currency(currency) + } } #[cfg_attr(test, mockable)] diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 93af23b0e..530d4be79 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -392,7 +392,8 @@ pub mod pallet { threshold: UnsignedFixedPoint, ) -> DispatchResult { ensure_root(origin)?; - Self::_set_secure_collateral_threshold(currency_pair, threshold); + Self::_set_secure_collateral_threshold(currency_pair.clone(), threshold); + ext::staking::add_reward_currency::(currency_pair.wrapped)?; Ok(()) } @@ -1729,7 +1730,7 @@ impl Pallet { currency_pair: DefaultVaultCurrencyPair, threshold: UnsignedFixedPoint, ) { - SecureCollateralThreshold::::insert(currency_pair, threshold); + SecureCollateralThreshold::::insert(currency_pair.clone(), threshold); } pub fn _set_premium_redeem_threshold( From 60dc3170e55da33aadc9496b833eb3bce2cf8fba Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 14:23:49 -0300 Subject: [PATCH 32/52] fix configs for new Staking value --- pallets/fee/src/mock.rs | 1 + pallets/issue/src/mock.rs | 1 + pallets/nomination/src/mock.rs | 1 + pallets/oracle/src/mock.rs | 4 ++++ pallets/redeem/src/mock.rs | 9 +++++---- pallets/replace/src/mock.rs | 1 + pallets/reward-distribution/src/ext.rs | 4 ++-- pallets/reward-distribution/src/mock.rs | 1 + pallets/staking/src/lib.rs | 8 ++++---- pallets/staking/src/mock.rs | 3 ++- pallets/vault-registry/src/mock.rs | 1 + testchain/runtime/src/lib.rs | 1 + 12 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index 6129151e5..f82d216e3 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -180,6 +180,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } parameter_types! { diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 7ce0f42b4..caa605123 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -272,6 +272,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } impl oracle::Config for Test { diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index e260abef1..2160c75b5 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -233,6 +233,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } impl security::Config for Test { diff --git a/pallets/oracle/src/mock.rs b/pallets/oracle/src/mock.rs index 835d641b7..dbb624b60 100644 --- a/pallets/oracle/src/mock.rs +++ b/pallets/oracle/src/mock.rs @@ -214,12 +214,16 @@ impl security::Config for Test { type WeightInfo = (); } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} impl staking::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } pub type TestEvent = RuntimeEvent; diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index 8a4e5464a..743505809 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -251,16 +251,17 @@ impl currency::Config for Test { type AmountCompatibility = MockAmountCompatibility; } +parameter_types! { + pub const MaxRewardCurrencies: u32= 10; +} + impl staking::Config for Test { type RuntimeEvent = TestEvent; type SignedFixedPoint = SignedFixedPoint; type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; -} - -parameter_types! { - pub const MaxRewardCurrencies: u32= 10; + type MaxRewardCurrencies = MaxRewardCurrencies; } impl pooled_rewards::Config for Test { diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index 87d57a045..e15359895 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -250,6 +250,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } parameter_types! { diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index a59477159..f36fbe1f9 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -51,12 +51,12 @@ pub(crate) mod pooled_rewards { #[cfg_attr(test, mockable)] pub(crate) mod staking { + use crate::{types::BalanceOf, DefaultVaultId}; use currency::CurrencyId; use frame_support::dispatch::DispatchError; + use sp_std::vec::Vec; use staking::Staking; - use crate::{types::BalanceOf, DefaultVaultId}; - pub fn distribute_reward( vault_id: &DefaultVaultId, amount: BalanceOf, diff --git a/pallets/reward-distribution/src/mock.rs b/pallets/reward-distribution/src/mock.rs index e8aa1d043..ee787eca7 100644 --- a/pallets/reward-distribution/src/mock.rs +++ b/pallets/reward-distribution/src/mock.rs @@ -223,6 +223,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } parameter_types! { diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index ed030b82f..fb96936dd 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -68,7 +68,7 @@ use sp_std::{cmp, convert::TryInto}; pub use pallet::*; use primitives::{BalanceToFixedPoint, TruncateFixedPointToInt, VaultCurrencyPair, VaultId}; - +use sp_std::vec::Vec; #[cfg(test)] mod mock; @@ -408,7 +408,7 @@ impl Pallet { .ok_or(ArithmeticError::Overflow)?; Ok::<_, DispatchError>(()) })?; - let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + let mut all_reward_currencies = Self::get_all_reward_currencies()?; all_reward_currencies.push(T::GetNativeCurrencyId::get()); for currency_id in all_reward_currencies { >::mutate( @@ -488,7 +488,7 @@ impl Pallet { // A slash means reward per token is no longer representative of the rewards // since `amount * reward_per_token` will be lost from the system. As such, // replenish rewards by the amount of reward lost with this slash - let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + let mut all_reward_currencies = Self::get_all_reward_currencies()?; all_reward_currencies.push(T::GetNativeCurrencyId::get()); for currency_id in all_reward_currencies { Self::increase_rewards( @@ -680,7 +680,7 @@ impl Pallet { Ok::<_, DispatchError>(()) })?; - let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; + let mut all_reward_currencies = Self::get_all_reward_currencies()?; all_reward_currencies.push(T::GetNativeCurrencyId::get()); for currency_id in all_reward_currencies { >::mutate( diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 3d8d8b06c..460be9ff9 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -75,14 +75,15 @@ impl frame_system::Config for Test { parameter_types! { pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; + pub const MaxRewardCurrencies: u32= 10; } - impl Config for Test { type RuntimeEvent = TestEvent; type SignedInner = SignedInner; type SignedFixedPoint = SignedFixedPoint; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } pub type Balance = u128; diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index 5efd15e73..c943e575c 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -330,6 +330,7 @@ impl staking::Config for Test { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } pub type TestEvent = RuntimeEvent; diff --git a/testchain/runtime/src/lib.rs b/testchain/runtime/src/lib.rs index 09d227804..613185346 100644 --- a/testchain/runtime/src/lib.rs +++ b/testchain/runtime/src/lib.rs @@ -336,6 +336,7 @@ impl staking::Config for Runtime { type SignedInner = SignedInner; type CurrencyId = CurrencyId; type GetNativeCurrencyId = GetNativeCurrencyId; + type MaxRewardCurrencies = MaxRewardCurrencies; } pub type OrganizationId = u128; From d789ef3da2866645a48a36ac93b0fda4fb6dda4d Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 14:34:56 -0300 Subject: [PATCH 33/52] addressed comments and fixes --- pallets/reward-distribution/src/lib.rs | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 60f928d99..0bf4a3411 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -197,25 +197,27 @@ impl Pallet { ) -> DispatchResult { let native_currency_id = T::GetNativeCurrencyId::get(); if reward_currency_id == native_currency_id { - let available_native_funds = T::Balances::total_balance(&Self::fee_pool_account_id()); - - if available_native_funds < reward { - //if available native is less than reward we transfer first from fee account, - //and mint the rest - let remaining = - reward.checked_sub(&available_native_funds).ok_or(Error::::Underflow)?; - let amount: currency::Amount = - Amount::new(available_native_funds, reward_currency_id); - amount.transfer(&Self::fee_pool_account_id(), &beneficiary)?; - T::Balances::deposit_creating(&beneficiary, remaining); - return Ok(()) - } else { - let amount: currency::Amount = Amount::new(reward, reward_currency_id); - return amount.transfer(&Self::fee_pool_account_id(), &beneficiary) + let available_native_funds = T::Balances::free_balance(&Self::fee_pool_account_id()); + + match reward.checked_sub(&available_native_funds) { + None => { + // Pay the whole reward from the fee pool + let amount: currency::Amount = Amount::new(reward, reward_currency_id); + return amount.transfer(&Self::fee_pool_account_id(), &beneficiary) + }, + Some(remaining) => { + // Use the available funds from the fee pool + let available_amount: currency::Amount = + Amount::new(available_native_funds, reward_currency_id); + available_amount.transfer(&Self::fee_pool_account_id(), &beneficiary)?; + // Mint the rest + T::Balances::deposit_creating(&beneficiary, remaining); + }, } + Ok(()) } else { - //we need no checking of available funds, since fee will ALWAYS have enough collected - //fees + //we need no checking of available funds, since the fee pool will ALWAYS have enough + // collected fees of the wrapped currencies let amount: currency::Amount = Amount::new(reward, reward_currency_id); amount.transfer(&Self::fee_pool_account_id(), &beneficiary) } @@ -299,7 +301,6 @@ impl Pallet { Ok(error_reward_accum) } - //TODO loop through all currencies not just these two!! fn withdraw_all_rewards_from_vault(vault_id: DefaultVaultId) -> DispatchResult { let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; all_reward_currencies.push(T::GetNativeCurrencyId::get()); From 4af8e86918b60c38ab5f847b4ca489fc4dc293a8 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 15:00:30 -0300 Subject: [PATCH 34/52] fix broken tests for staking pallet --- pallets/staking/src/lib.rs | 8 +++++++- pallets/staking/src/tests.rs | 14 +++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index fb96936dd..40f959359 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -1030,6 +1030,7 @@ pub mod migration { // withdraw_reward] // step 1: initial (normal) flow + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &VAULT.account_id, fixed!(50))); assert_ok!(Staking::distribute_reward(DEFAULT_WRAPPED_CURRENCY, &VAULT, fixed!(10000))); assert_ok!( @@ -1087,6 +1088,7 @@ pub mod migration { } fn assert_total_rewards(amount: i128) { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); use mock::*; assert_eq!( Staking::total_rewards(DEFAULT_WRAPPED_CURRENCY, (0, VAULT.clone())), @@ -1098,6 +1100,7 @@ pub mod migration { fn test_total_rewards_tracking_in_buggy_code() { use mock::*; run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); setup_broken_state(); assert_total_rewards(12000); @@ -1141,6 +1144,7 @@ pub mod migration { fn test_migration() { use mock::*; run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); let fee_pool_account_id = 23; assert_ok!( as MultiCurrency< @@ -1186,7 +1190,7 @@ pub mod migration { /// despite the slash bug (it will withdraw an incorrect but non-zero amount) fn setup_broken_state_with_withdrawable_reward() { use mock::*; - + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); setup_broken_state(); assert_total_rewards(12000); assert_ok!(Staking::distribute_reward( @@ -1201,6 +1205,7 @@ pub mod migration { fn test_broken_state_with_withdrawable_amount() { use mock::*; run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); setup_broken_state_with_withdrawable_reward(); assert_total_rewards(1_012_000); @@ -1222,6 +1227,7 @@ pub mod migration { fn test_migration_of_account_with_withdrawable_amount() { use mock::*; run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); let fee_pool_account_id = 23; assert_ok!( as MultiCurrency< diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index 89f2a5e85..5549966be 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -22,7 +22,8 @@ fn reproduce_broken_state() { let f = |x: i128| { SignedFixedPoint::from_inner(x) }; - + assert_ok!(Staking::add_reward_currency(VAULT.wrapped_currency())); + assert_ok!(Staking::add_reward_currency(currency)); // state at block 0x5aaf4dc2ca1a1043e2c37ba85a1d1487a0e4cc79d3beb30e6e646d2190223851 RewardPerToken::::insert(currency, (0, VAULT), f(8328262397106661114)); RewardTally::::insert(currency, (0, VAULT, account), f(594980318984627591665452302579139)); @@ -65,7 +66,6 @@ fn slash_stake_does_not_break_state() { let f = |x: i128| { SignedFixedPoint::from_inner(x) }; - // state at block 0x5aaf4dc2ca1a1043e2c37ba85a1d1487a0e4cc79d3beb30e6e646d2190223851 RewardPerToken::::insert(currency, (0, VAULT), f(8328262397106661114)); RewardTally::::insert(currency, (0, VAULT, account), f(594980318984627591665452302579139)); @@ -102,6 +102,8 @@ fn slash_stake_does_not_break_state() { #[test] fn should_stake_and_earn_rewards() { run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(50))); assert_ok!(Staking::deposit_stake(&VAULT, &BOB.account_id, fixed!(50))); assert_ok!(Staking::distribute_reward(DEFAULT_WRAPPED_CURRENCY, &VAULT, fixed!(100))); @@ -127,7 +129,7 @@ fn continues_functioning_after_slash() { // without the `apply_slash` in withdraw_rewards, the following sequence fails in the last // step: [distribute_reward, slash_stake, withdraw_reward, distribute_reward, // withdraw_reward] - + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); // step 1: initial (normal) flow assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(50))); assert_ok!(Staking::distribute_reward(DEFAULT_WRAPPED_CURRENCY, &VAULT, fixed!(10000))); @@ -168,6 +170,7 @@ fn continues_functioning_after_slash() { #[test] fn should_stake_and_distribute_and_withdraw() { run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(10000))); assert_ok!(Staking::deposit_stake(&VAULT, &BOB.account_id, fixed!(10000))); @@ -218,6 +221,7 @@ fn should_stake_and_distribute_and_withdraw() { #[test] fn should_stake_and_withdraw_rewards() { run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(100))); assert_ok!(Staking::distribute_reward(DEFAULT_WRAPPED_CURRENCY, &VAULT, fixed!(100))); assert_ok!( @@ -235,6 +239,7 @@ fn should_stake_and_withdraw_rewards() { #[test] fn should_not_withdraw_stake_if_balance_insufficient() { run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(100))); assert_ok!(Staking::compute_stake(&VAULT, &ALICE.account_id), 100); assert_err!( @@ -247,6 +252,7 @@ fn should_not_withdraw_stake_if_balance_insufficient() { #[test] fn should_not_withdraw_stake_if_balance_insufficient_after_slashing() { run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(100))); assert_ok!(Staking::compute_stake(&VAULT, &ALICE.account_id), 100); assert_ok!(Staking::slash_stake(&VAULT, fixed!(100))); @@ -262,6 +268,7 @@ fn should_not_withdraw_stake_if_balance_insufficient_after_slashing() { fn should_force_refund() { run_test(|| { let mut nonce = Staking::nonce(&VAULT); + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &VAULT.account_id, fixed!(100))); assert_ok!(Staking::deposit_stake(&VAULT, &ALICE.account_id, fixed!(100))); assert_ok!(Staking::slash_stake(&VAULT, fixed!(100))); @@ -349,6 +356,7 @@ fn should_compute_stake_after_adjustments() { // this replicates a failing integration test due to repeated // deposits and slashing which led to incorrect stake run_test(|| { + assert_ok!(Staking::add_reward_currency(DEFAULT_WRAPPED_CURRENCY)); assert_ok!(Staking::deposit_stake(&VAULT, &VAULT.account_id, fixed!(100))); assert_ok!(Staking::deposit_stake(&VAULT, &VAULT.account_id, fixed!(1152923504604516976))); assert_ok!(Staking::slash_stake(&VAULT, fixed!(1152923504604516976 + 100))); From 5abd90003b9dca1b798b5fcec5399ec3ede80b80 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 18 Oct 2023 18:08:04 -0300 Subject: [PATCH 35/52] pallet integration tests for per block reward distribution --- pallets/reward-distribution/src/lib.rs | 1 + pallets/vault-registry/src/mock.rs | 42 +++++-- pallets/vault-registry/src/tests.rs | 151 ++++++++++++++++++++++++- 3 files changed, 182 insertions(+), 12 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 0bf4a3411..8d3b278bf 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -280,6 +280,7 @@ impl Pallet { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } + //distribute the rewards to each collateral pool let mut error_reward_accum = BalanceOf::::zero(); for (currency_id, stake) in total_stakes.into_iter() { diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index c943e575c..42cec79a6 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -9,6 +9,7 @@ use oracle::{ oracle_mock::{Data, DataKey, MockConvertMoment, MockConvertPrice, MockOracleKeyConvertor}, CoinInfo, DataFeeder, DataProvider, DiaOracle, PriceInfo, TimestampedValue, }; + use orml_currencies::BasicCurrencyAdapter; use orml_traits::parameter_type_with_key; use primitives::oracle::Key; @@ -24,7 +25,7 @@ use std::cell::RefCell; pub use currency::testing_constants::{ DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, DEFAULT_WRAPPED_CURRENCY, }; -pub use primitives::CurrencyId; +pub use primitives::{CurrencyId, CurrencyId::XCM}; use primitives::{VaultCurrencyPair, VaultId}; use crate as vault_registry; @@ -136,6 +137,9 @@ pub const DEFAULT_CURRENCY_PAIR: VaultCurrencyPair = VaultCurrencyPa wrapped: DEFAULT_WRAPPED_CURRENCY, }; +pub const OTHER_CURRENCY_PAIR: VaultCurrencyPair = + VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }; + parameter_types! { pub const GetCollateralCurrencyId: CurrencyId = DEFAULT_COLLATERAL_CURRENCY; pub const GetNativeCurrencyId: CurrencyId = DEFAULT_NATIVE_CURRENCY; @@ -261,14 +265,19 @@ parameter_types! { pub struct OracleApiMock {} impl oracle::OracleApi for OracleApiMock { fn currency_to_usd( - _amount: &Balance, + amount: &Balance, currency_id: &CurrencyId, ) -> Result { let _native_currency = GetNativeCurrencyId::get(); - match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), - } + let amount_in_usd = match currency_id { + &XCM(0) => amount * 5, + &XCM(1) => amount * 10, + &XCM(2) => amount * 15, + &XCM(3) => amount * 20, + &XCM(4) => amount * 35, + _native_currency => amount * 3, + }; + Ok(amount_in_usd) } } @@ -360,6 +369,14 @@ pub const RICH_ID: VaultId = VaultId { wrapped: DEFAULT_WRAPPED_CURRENCY, }, }; +pub const ID_COLLATERAL_21: VaultId = VaultId { + account_id: 6, + currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }, +}; +pub const ID_COLLATERAL_22: VaultId = VaultId { + account_id: 7, + currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }, +}; pub const DEFAULT_COLLATERAL: u128 = 100000; pub const RICH_COLLATERAL: u128 = DEFAULT_COLLATERAL + 100000; pub const MULTI_VAULT_TEST_IDS: [u64; 4] = [100, 101, 102, 103]; @@ -373,9 +390,12 @@ impl ExtBuilder { // Parameters to be set in tests vault_registry::GenesisConfig:: { - minimum_collateral_vault: vec![(DEFAULT_COLLATERAL_CURRENCY, 0)], + minimum_collateral_vault: vec![(DEFAULT_COLLATERAL_CURRENCY, 0), (XCM(1), 0)], punishment_delay: 0, - system_collateral_ceiling: vec![(DEFAULT_CURRENCY_PAIR, 1_000_000_000_000)], + system_collateral_ceiling: vec![ + (DEFAULT_CURRENCY_PAIR, 1_000_000_000_000), + (OTHER_CURRENCY_PAIR, 1_000_000_000_000), + ], secure_collateral_threshold: vec![(DEFAULT_CURRENCY_PAIR, UnsignedFixedPoint::one())], premium_redeem_threshold: vec![(DEFAULT_CURRENCY_PAIR, UnsignedFixedPoint::one())], liquidation_collateral_threshold: vec![( @@ -394,6 +414,8 @@ impl ExtBuilder { (DEFAULT_ID.account_id, DEFAULT_COLLATERAL_CURRENCY, DEFAULT_COLLATERAL), (OTHER_ID.account_id, DEFAULT_COLLATERAL_CURRENCY, DEFAULT_COLLATERAL), (RICH_ID.account_id, DEFAULT_COLLATERAL_CURRENCY, RICH_COLLATERAL), + (ID_COLLATERAL_21.account_id, XCM(1), DEFAULT_COLLATERAL), + (ID_COLLATERAL_22.account_id, XCM(1), DEFAULT_COLLATERAL), (MULTI_VAULT_TEST_IDS[0], DEFAULT_COLLATERAL_CURRENCY, MULTI_VAULT_TEST_COLLATERAL), (MULTI_VAULT_TEST_IDS[1], DEFAULT_COLLATERAL_CURRENCY, MULTI_VAULT_TEST_COLLATERAL), (MULTI_VAULT_TEST_IDS[2], DEFAULT_COLLATERAL_CURRENCY, MULTI_VAULT_TEST_COLLATERAL), @@ -411,6 +433,10 @@ pub(crate) fn set_default_thresholds() { VaultRegistry::_set_secure_collateral_threshold(DEFAULT_CURRENCY_PAIR, secure); VaultRegistry::_set_premium_redeem_threshold(DEFAULT_CURRENCY_PAIR, premium); VaultRegistry::_set_liquidation_collateral_threshold(DEFAULT_CURRENCY_PAIR, liquidation); + + VaultRegistry::_set_secure_collateral_threshold(OTHER_CURRENCY_PAIR, secure); + VaultRegistry::_set_premium_redeem_threshold(OTHER_CURRENCY_PAIR, premium); + VaultRegistry::_set_liquidation_collateral_threshold(OTHER_CURRENCY_PAIR, liquidation); } pub fn run_test(test: T) diff --git a/pallets/vault-registry/src/tests.rs b/pallets/vault-registry/src/tests.rs index 7f0e41197..6acc8a21f 100644 --- a/pallets/vault-registry/src/tests.rs +++ b/pallets/vault-registry/src/tests.rs @@ -1,7 +1,12 @@ use codec::Decode; +use currency::{testing_constants::get_wrapped_currency_id, Amount}; use frame_support::{assert_err, assert_noop, assert_ok, error::BadOrigin}; +use frame_system::RawOrigin; use mocktopus::mocking::*; +use pooled_rewards::RewardsApi; use pretty_assertions::assert_eq; +use primitives::{StellarPublicKeyRaw, VaultCurrencyPair, VaultId}; +use security::Pallet as Security; use sp_arithmetic::{traits::One, FixedPointNumber, FixedU128}; use sp_core::U256; use sp_runtime::{ @@ -10,10 +15,6 @@ use sp_runtime::{ }; use sp_std::convert::TryInto; -use currency::{testing_constants::get_wrapped_currency_id, Amount}; -use primitives::{StellarPublicKeyRaw, VaultCurrencyPair, VaultId}; -use security::Pallet as Security; - use crate::{ ext, mock::*, @@ -1711,3 +1712,145 @@ fn test_offchain_worker_unsigned_transaction_submission() { ); }) } + +#[test] +fn integration_single_vault_receives_per_block_reward() { + run_test(|| { + //set up reward values + let reward_per_block: u64 = 1000u64; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); + //register vault and issue tokens. + let issue_tokens: u128 = DEFAULT_COLLATERAL / 10 / 2; // = 5 + let id = create_vault_and_issue_tokens(issue_tokens, DEFAULT_COLLATERAL, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id.collateral_currency(), &id), + Ok(DEFAULT_COLLATERAL) + ); + assert_eq!(>::free_balance(&id.account_id), 0u128); + + //distribute fee rewards + >::execute_on_init(2u32.into()); + //collect rewards + let origin = RuntimeOrigin::signed(id.account_id); + assert_ok!(>::collect_reward( + origin.into(), + id.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + assert_eq!( + >::free_balance(&id.account_id), + reward_per_block.into() + ); + }); +} + +#[test] +fn integration_multiple_vault_same_collateral_per_block_reward() { + run_test(|| { + //set up reward values + let reward_per_block: u64 = 100000u64; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); + + //register vaults and issue tokens. + let issue_tokens: u128 = 5; + + let collateral_vault_1 = 1000u128; + let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id.collateral_currency(), &id), + Ok(collateral_vault_1) + ); + + let collateral_vault_2 = 5000u128; + let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), + Ok(collateral_vault_2) + ); + + //distribute fee rewards + >::execute_on_init(2u32.into()); + //collect rewards + let origin = RuntimeOrigin::signed(id.account_id); + assert_ok!(>::collect_reward( + origin.into(), + id.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + //expected value = (1000*5/(5000*5 + 1000*5))*100000 = 16666 + assert_eq!(>::free_balance(&id.account_id), 16666u128.into()); + }); +} + +#[test] +fn integration_multiple_vault_multiple_collateral_per_block_reward() { + run_test(|| { + //set up reward values and threshold + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + let reward_per_block: u64 = 100000u64; + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); + + //register vaults and issue tokens. + let issue_tokens: u128 = 5; + + let collateral_vault_1 = 1000u128; + let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id.collateral_currency(), &id), + Ok(collateral_vault_1) + ); + + let collateral_vault_2 = 5000u128; + let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), + Ok(collateral_vault_2) + ); + + let collateral_vault_3 = 3000u128; + let id3 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_3, ID_COLLATERAL_21); + assert_eq!( + ::VaultRewards::get_stake(&id3.collateral_currency(), &id3), + Ok(collateral_vault_3) + ); + + let collateral_vault_4 = 2000u128; + let id4 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_4, ID_COLLATERAL_22); + assert_eq!( + ::VaultRewards::get_stake(&id4.collateral_currency(), &id4), + Ok(collateral_vault_4) + ); + + //distribute fee rewards + >::execute_on_init(2u32.into()); + //collect rewards + let origin = RuntimeOrigin::signed(id4.account_id); + assert_ok!(>::collect_reward( + origin.into(), + id4.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + //expected value = ((2000*10 + 3000*10)/(5000*5 + 1000*5 + 2000*10 + + // 3000*10))*(2000/(3000+2000))*100000 = 25000 + assert_eq!( + >::free_balance(&id4.account_id), + 25000u128.into() + ); + }); +} From c500e934695c58de2cff8523b1166ca59d96f4f0 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 19 Oct 2023 13:10:34 -0300 Subject: [PATCH 36/52] fee reward distribution tests --- pallets/currency/src/testing_constants.rs | 16 +++ pallets/issue/src/mock.rs | 67 ++++++----- pallets/issue/src/tests.rs | 139 ++++++++++++++++++++++ pallets/vault-registry/src/tests.rs | 1 + 4 files changed, 194 insertions(+), 29 deletions(-) diff --git a/pallets/currency/src/testing_constants.rs b/pallets/currency/src/testing_constants.rs index db087762c..6ab317b93 100644 --- a/pallets/currency/src/testing_constants.rs +++ b/pallets/currency/src/testing_constants.rs @@ -14,6 +14,22 @@ pub const DEFAULT_WRAPPED_CURRENCY: CurrencyId = CurrencyId::AlphaNum4( ], ); +pub const WRAPPED_CURRENCY2: CurrencyId = CurrencyId::AlphaNum4( + *b"USDT", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + +pub const WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( + *b"EURC", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + pub fn get_wrapped_currency_id() -> CurrencyId { DEFAULT_WRAPPED_CURRENCY } diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index caa605123..228922daf 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; +pub use currency::{testing_constants::*, Amount}; use frame_support::{ assert_ok, parameter_types, traits::{ConstU32, ConstU64, Everything, GenesisBuild}, @@ -13,7 +14,8 @@ use oracle::{ }; use orml_currencies::BasicCurrencyAdapter; use orml_traits::parameter_type_with_key; -use primitives::oracle::Key; +pub use primitives::CurrencyId; +use primitives::{oracle::Key, AmountCompatibility, CurrencyId::XCM, VaultCurrencyPair, VaultId}; use sp_arithmetic::{FixedI128, FixedPointNumber, FixedU128}; use sp_core::H256; use sp_runtime::{ @@ -22,15 +24,6 @@ use sp_runtime::{ DispatchError, Perquintill, }; -pub use currency::{ - testing_constants::{ - DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, DEFAULT_WRAPPED_CURRENCY, - }, - Amount, -}; -pub use primitives::CurrencyId; -use primitives::{AmountCompatibility, VaultCurrencyPair, VaultId}; - use crate as issue; use crate::{Config, Error}; @@ -329,17 +322,21 @@ parameter_types! { pub struct OracleApiMock {} impl oracle::OracleApi for OracleApiMock { fn currency_to_usd( - _amount: &Balance, + amount: &Balance, currency_id: &CurrencyId, ) -> Result { let _native_currency = GetNativeCurrencyId::get(); - match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), - } + let amount_in_usd = match currency_id { + &XCM(0) => amount * 5, + &XCM(1) => amount * 10, + &XCM(2) => amount * 15, + &XCM(3) => amount * 20, + &XCM(4) => amount * 35, + _native_currency => amount * 3, + }; + Ok(amount_in_usd) } } - impl reward_distribution::Config for Test { type RuntimeEvent = TestEvent; type WeightInfo = reward_distribution::SubstrateWeight; @@ -391,6 +388,13 @@ pub const VAULT: VaultId = VaultId { wrapped: DEFAULT_WRAPPED_CURRENCY, }, }; +pub const VAULT2: VaultId = VaultId { + account_id: 3, + currencies: VaultCurrencyPair { + collateral: DEFAULT_COLLATERAL_CURRENCY, + wrapped: WRAPPED_CURRENCY2, + }, +}; pub const ALICE_BALANCE: u128 = 1_000_000; pub const BOB_BALANCE: u128 = 1_000_000; @@ -440,22 +444,26 @@ impl ExtBuilder { collateral: DEFAULT_COLLATERAL_CURRENCY, wrapped: DEFAULT_WRAPPED_CURRENCY, }; + const PAIR2: VaultCurrencyPair = VaultCurrencyPair { + collateral: DEFAULT_COLLATERAL_CURRENCY, + wrapped: WRAPPED_CURRENCY2, + }; vault_registry::GenesisConfig:: { minimum_collateral_vault: vec![(DEFAULT_COLLATERAL_CURRENCY, 0)], punishment_delay: 8, - system_collateral_ceiling: vec![(PAIR, 1_000_000_000_000)], - secure_collateral_threshold: vec![( - PAIR, - UnsignedFixedPoint::checked_from_rational(200, 100).unwrap(), - )], - premium_redeem_threshold: vec![( - PAIR, - UnsignedFixedPoint::checked_from_rational(120, 100).unwrap(), - )], - liquidation_collateral_threshold: vec![( - PAIR, - UnsignedFixedPoint::checked_from_rational(110, 100).unwrap(), - )], + system_collateral_ceiling: vec![(PAIR, 1_000_000_000_000), (PAIR2, 1_000_000_000_000)], + secure_collateral_threshold: vec![ + (PAIR, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR2, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + ], + premium_redeem_threshold: vec![ + (PAIR, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR2, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + ], + liquidation_collateral_threshold: vec![ + (PAIR, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR2, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + ], } .assimilate_storage(&mut storage) .unwrap(); @@ -471,6 +479,7 @@ impl ExtBuilder { vec![ (USER, currency_id, ALICE_BALANCE), (VAULT.account_id, currency_id, BOB_BALANCE), + (VAULT2.account_id, currency_id, BOB_BALANCE), ] }) .collect(), diff --git a/pallets/issue/src/tests.rs b/pallets/issue/src/tests.rs index 39b9426c4..e2dfc90da 100644 --- a/pallets/issue/src/tests.rs +++ b/pallets/issue/src/tests.rs @@ -624,3 +624,142 @@ fn test_request_issue_reset_interval_and_succeeds_with_rate_limit() { assert_eq!(>::get(), BalanceOf::::zero()); }) } +mod integration_tests { + use super::*; + use pooled_rewards::RewardsApi; + fn get_reward_for_vault(vault: &DefaultVaultId, reward_currency: CurrencyId) -> Balance { + <::VaultRewards as RewardsApi< + CurrencyId, + DefaultVaultId, + Balance, + CurrencyId, + >>::compute_reward(&vault.collateral_currency(), vault, reward_currency) + .unwrap() + } + + fn register_vault_with_collateral(vault: &DefaultVaultId, collateral_amount: Balance) { + let origin = RuntimeOrigin::signed(vault.account_id); + + assert_ok!(VaultRegistry::register_public_key(origin.clone(), DEFAULT_STELLAR_PUBLIC_KEY)); + assert_ok!(VaultRegistry::register_vault( + origin, + vault.currencies.clone(), + collateral_amount + )); + } + fn wrapped_with_custom_curr(amount: u128, currency: CurrencyId) -> Amount { + Amount::new(amount, currency) + } + fn setup_execute_with_vault( + issue_amount: Balance, + issue_fee: Balance, + griefing_collateral: Balance, + amount_transferred: Balance, + vault: DefaultVaultId, + ) -> Result { + let vault_clone = vault.clone(); + let vault_clone_2 = vault.clone(); + ext::vault_registry::get_active_vault_from_id:: + .mock_safe(move |_| MockResult::Return(Ok(init_zero_vault(vault_clone.clone())))); + ext::vault_registry::issue_tokens::.mock_safe(|_, _| MockResult::Return(Ok(()))); + ext::vault_registry::is_vault_liquidated:: + .mock_safe(|_| MockResult::Return(Ok(false))); + + ext::fee::get_issue_fee::.mock_safe(move |_| { + MockResult::Return(Ok(wrapped_with_custom_curr( + issue_fee, + vault_clone_2.clone().wrapped_currency(), + ))) + }); + ext::fee::get_issue_griefing_collateral:: + .mock_safe(move |_| MockResult::Return(Ok(griefing(griefing_collateral)))); + + let issue_id = + request_issue_ok_with_address(USER, issue_amount, vault, RANDOM_STELLAR_PUBLIC_KEY)?; + >::set_active_block_number(5); + + ext::stellar_relay::validate_stellar_transaction:: + .mock_safe(move |_, _, _| MockResult::Return(Ok(()))); + ext::currency::get_amount_from_transaction_envelope::.mock_safe( + move |_, _, currency| MockResult::Return(Ok(Amount::new(amount_transferred, currency))), + ); + + Ok(issue_id) + } + + //In these tests in particular we are testing that a given fee is distributed to + //the pooled-reward pallet depending on the collateral. + //These tests WILL NOT test the distribution from pooled-reward to staking. + #[test] + fn integration_single_vault_gets_fee_rewards_when_issuing() { + run_test(|| { + //set up vault + let collateral: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral); + //execute the issue + + let issue_asset = VAULT.wrapped_currency(); + let issue_amount = 30; + let issue_fee = 1; + let griefing_collateral = 1; + let amount_transferred = 30; + let issue_id = + setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) + .unwrap(); + + assert_ok!(execute_issue(USER, &issue_id)); + + //ensure that the current rewards equal to fee + assert_eq!(get_reward_for_vault(&VAULT, issue_asset), issue_fee); + }) + } + + #[test] + fn integration_multiple_vaults_same_collateral_gets_fee_rewards_when_issuing() { + run_test(|| { + //set up the vaults + let collateral1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral1); + + let collateral2: u128 = 2000; + register_vault_with_collateral(&VAULT2, collateral2); + + //execute the issue + let issue_asset = VAULT.wrapped_currency(); + let issue_amount = 30; + let issue_fee = 10; + let griefing_collateral = 1; + let amount_transferred = 30; + let issue_id = + setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) + .unwrap(); + + assert_ok!(execute_issue(USER, &issue_id)); + + //execute the issue on the other currency + let issue_asset2 = VAULT2.wrapped_currency(); + let issue_amount2 = 30; + let issue_fee2 = 20; + let griefing_collateral2 = 1; + let amount_transferred2 = 30; + let issue_id2 = setup_execute_with_vault( + issue_amount2, + issue_fee2, + griefing_collateral2, + amount_transferred2, + VAULT2.clone(), + ) + .unwrap(); + + assert_ok!(execute_issue(USER, &issue_id2)); + + //ensure that the current rewards equal to fee + //Example: we expect for reward(vault1, asset1) = floor( 10*(1000/3000) ) = 3 + assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 3); + assert_eq!(get_reward_for_vault(&VAULT2, issue_asset), 6); + + assert_eq!(get_reward_for_vault(&VAULT, issue_asset2), 6); + assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 13); + }) + } +} diff --git a/pallets/vault-registry/src/tests.rs b/pallets/vault-registry/src/tests.rs index 6acc8a21f..1d732564f 100644 --- a/pallets/vault-registry/src/tests.rs +++ b/pallets/vault-registry/src/tests.rs @@ -1846,6 +1846,7 @@ fn integration_multiple_vault_multiple_collateral_per_block_reward() { None, )); + //Explicit calc for the expected reward on this test for id4 //expected value = ((2000*10 + 3000*10)/(5000*5 + 1000*5 + 2000*10 + // 3000*10))*(2000/(3000+2000))*100000 = 25000 assert_eq!( From 8f9976712b32eb58c63fc5306f84c54143fd0e9f Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 19 Oct 2023 15:06:22 -0300 Subject: [PATCH 37/52] multiple vaults multiple collaterals works --- pallets/issue/src/mock.rs | 64 +++++++++++++++++---- pallets/issue/src/tests.rs | 111 +++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 11 deletions(-) diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 228922daf..3db686672 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -396,6 +396,34 @@ pub const VAULT2: VaultId = VaultId { }, }; +pub const VAULT3: VaultId = VaultId { + account_id: 5, + currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: WRAPPED_CURRENCY3 }, +}; + +pub const VAULT4: VaultId = VaultId { + account_id: 6, + currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }, +}; + +pub const VAULT5: VaultId = VaultId { + account_id: 7, + currencies: VaultCurrencyPair { collateral: XCM(2), wrapped: WRAPPED_CURRENCY2 }, +}; + +const PAIR: VaultCurrencyPair = VaultCurrencyPair { + collateral: DEFAULT_COLLATERAL_CURRENCY, + wrapped: DEFAULT_WRAPPED_CURRENCY, +}; +const PAIR2: VaultCurrencyPair = + VaultCurrencyPair { collateral: DEFAULT_COLLATERAL_CURRENCY, wrapped: WRAPPED_CURRENCY2 }; +const PAIR3: VaultCurrencyPair = + VaultCurrencyPair { collateral: XCM(1), wrapped: WRAPPED_CURRENCY3 }; +const PAIR4: VaultCurrencyPair = + VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }; +const PAIR5: VaultCurrencyPair = + VaultCurrencyPair { collateral: XCM(2), wrapped: WRAPPED_CURRENCY2 }; + pub const ALICE_BALANCE: u128 = 1_000_000; pub const BOB_BALANCE: u128 = 1_000_000; @@ -440,29 +468,40 @@ impl ExtBuilder { .assimilate_storage(&mut storage) .unwrap(); - const PAIR: VaultCurrencyPair = VaultCurrencyPair { - collateral: DEFAULT_COLLATERAL_CURRENCY, - wrapped: DEFAULT_WRAPPED_CURRENCY, - }; - const PAIR2: VaultCurrencyPair = VaultCurrencyPair { - collateral: DEFAULT_COLLATERAL_CURRENCY, - wrapped: WRAPPED_CURRENCY2, - }; vault_registry::GenesisConfig:: { - minimum_collateral_vault: vec![(DEFAULT_COLLATERAL_CURRENCY, 0)], + minimum_collateral_vault: vec![ + (DEFAULT_COLLATERAL_CURRENCY, 0), + (XCM(1), 0), + (XCM(2), 0), + ], punishment_delay: 8, - system_collateral_ceiling: vec![(PAIR, 1_000_000_000_000), (PAIR2, 1_000_000_000_000)], + system_collateral_ceiling: vec![ + (PAIR, 1_000_000_000_000), + (PAIR2, 1_000_000_000_000), + (PAIR3, 1_000_000_000_000), + (PAIR4, 1_000_000_000_000), + (PAIR5, 1_000_000_000_000), + ], secure_collateral_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), (PAIR2, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR3, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR4, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR5, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), ], premium_redeem_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), (PAIR2, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR3, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR4, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR5, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), ], liquidation_collateral_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), (PAIR2, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR3, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR4, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR5, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), ], } .assimilate_storage(&mut storage) @@ -473,13 +512,16 @@ impl ExtBuilder { pub fn build() -> sp_io::TestExternalities { ExtBuilder::build_with(orml_tokens::GenesisConfig:: { - balances: vec![DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY] + balances: vec![DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, XCM(1), XCM(2)] .into_iter() .flat_map(|currency_id| { vec![ (USER, currency_id, ALICE_BALANCE), (VAULT.account_id, currency_id, BOB_BALANCE), (VAULT2.account_id, currency_id, BOB_BALANCE), + (VAULT3.account_id, currency_id, BOB_BALANCE), + (VAULT4.account_id, currency_id, BOB_BALANCE), + (VAULT5.account_id, currency_id, BOB_BALANCE), ] }) .collect(), diff --git a/pallets/issue/src/tests.rs b/pallets/issue/src/tests.rs index e2dfc90da..3e800652f 100644 --- a/pallets/issue/src/tests.rs +++ b/pallets/issue/src/tests.rs @@ -762,4 +762,115 @@ mod integration_tests { assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 13); }) } + + #[test] + fn integration_multiple_vaults_many_collateral_gets_fee_rewards_when_issuing() { + run_test(|| { + //set up the vaults + let collateral1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral1); + + let collateral2: u128 = 2000; + register_vault_with_collateral(&VAULT2, collateral2); + + let collateral3: u128 = 1500; + register_vault_with_collateral(&VAULT3, collateral3); + + let collateral4: u128 = 1700; + register_vault_with_collateral(&VAULT4, collateral4); + + let collateral5: u128 = 800; + register_vault_with_collateral(&VAULT5, collateral5); + + //execute the issue + let issue_asset = VAULT.wrapped_currency(); + let issue_amount = 3000; + let issue_fee = 1000; + let griefing_collateral = 1; + let amount_transferred = 3000; + let issue_id = + setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id)); + + //execute the issue on the other currency + let issue_asset2 = VAULT2.wrapped_currency(); + let issue_amount2 = 3000; + let issue_fee2 = 500; + let griefing_collateral2 = 1; + let amount_transferred2 = 3000; + let issue_id2 = setup_execute_with_vault( + issue_amount2, + issue_fee2, + griefing_collateral2, + amount_transferred2, + VAULT2.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id2)); + + let issue_asset3 = VAULT3.wrapped_currency(); + let issue_amount3 = 3000; + let issue_fee3 = 600; + let griefing_collateral3 = 1; + let amount_transferred3 = 3000; + let issue_id3 = setup_execute_with_vault( + issue_amount3, + issue_fee3, + griefing_collateral3, + amount_transferred3, + VAULT3.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id3)); + + let _issue_asset4 = VAULT4.wrapped_currency(); + let issue_amount4 = 3000; + let issue_fee4 = 400; + let griefing_collateral4 = 1; + let amount_transferred4 = 3000; + let issue_id4 = setup_execute_with_vault( + issue_amount4, + issue_fee4, + griefing_collateral4, + amount_transferred4, + VAULT4.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id4)); + + let _issue_asset5 = VAULT5.wrapped_currency(); + let issue_amount5 = 3000; + let issue_fee5 = 700; + let griefing_collateral5 = 1; + let amount_transferred5 = 3000; + let issue_id5 = setup_execute_with_vault( + issue_amount5, + issue_fee5, + griefing_collateral5, + amount_transferred5, + VAULT5.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id5)); + + //ensure that the current rewards equal to fee + //Example: we expect for reward(vault1, asset1) = floor( + //floor((1000/3000)*floor( 1400*(3000*5/(3000*5+3200*10+800*15)) )) = 118 + //where asset1 = asset4 + assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 118); + + //withouth floor(), should actually exactly be 237.25. In this case + //it is 236. The discrepancy is due to two consecutive roundings + assert_eq!(get_reward_for_vault(&VAULT2, issue_asset), 236); + assert_eq!(get_reward_for_vault(&VAULT3, issue_asset), 355); + assert_eq!(get_reward_for_vault(&VAULT4, issue_asset), 402); + assert_eq!(get_reward_for_vault(&VAULT5, issue_asset), 284); + + assert_eq!(get_reward_for_vault(&VAULT, issue_asset2), 101); + //assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 13); + + assert_eq!(get_reward_for_vault(&VAULT, issue_asset3), 50); + }) + } } From 92f4ca8f3d2643ab5b3495de1159821588c49bac Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 19 Oct 2023 17:29:03 -0300 Subject: [PATCH 38/52] fix critical bug in reward collection --- pallets/reward-distribution/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 0bf4a3411..e99a55eb9 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -176,15 +176,15 @@ pub mod pallet { ext::staking::distribute_reward::(&vault_id, reward, reward_currency_id)?; //withdraw the reward for specific nominator - let rewards = + let caller_rewards = ext::staking::withdraw_reward::(&vault_id, &caller, index, reward_currency_id)?; - if rewards == (BalanceOf::::zero()) { + if caller_rewards == (BalanceOf::::zero()) { return Err(Error::::NoRewardsForAccount.into()) } //transfer rewards - Self::transfer_reward(reward_currency_id, reward, caller) + Self::transfer_reward(reward_currency_id, caller_rewards, caller) } } } From 386ba48e1404972fa1a7aa1ef32bee6d027bfdac Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 19 Oct 2023 17:40:46 -0300 Subject: [PATCH 39/52] multiple vault, collaterals and nominators works --- Cargo.lock | 1 + pallets/issue/Cargo.toml | 2 + pallets/issue/src/mock.rs | 17 ++ pallets/issue/src/tests.rs | 233 +++++++++++++++++-------- pallets/reward-distribution/src/lib.rs | 7 +- 5 files changed, 183 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c26de5207..37759c0c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3480,6 +3480,7 @@ dependencies = [ "hex", "log", "mocktopus", + "nomination", "oracle", "orml-currencies", "orml-tokens", diff --git a/pallets/issue/Cargo.toml b/pallets/issue/Cargo.toml index e23b88000..3ca3aac97 100644 --- a/pallets/issue/Cargo.toml +++ b/pallets/issue/Cargo.toml @@ -35,6 +35,7 @@ stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } reward-distribution = { path = "../reward-distribution", default-features = false } +nomination = {path = "../nomination", default-features = false} primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -88,6 +89,7 @@ std = [ "orml-tokens/std", "orml-traits/std", "reward-distribution/std", + "nomination/std" ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index 3db686672..eb4473145 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -56,6 +56,7 @@ frame_support::construct_runtime!( Oracle: oracle::{Pallet, Call, Config, Storage, Event}, Fee: fee::{Pallet, Call, Config, Storage}, Staking: staking::{Pallet, Storage, Event}, + Nomination: nomination::{Pallet, Call, Config, Storage, Event}, VaultRegistry: vault_registry::{Pallet, Call, Config, Storage, Event}, } ); @@ -351,6 +352,11 @@ impl reward_distribution::Config for Test { type FeePalletId = FeePalletId; } +impl nomination::Config for Test { + type RuntimeEvent = TestEvent; + type WeightInfo = nomination::SubstrateWeight; +} + parameter_types! { pub const MinimumPeriod: Moment = 5; } @@ -381,6 +387,9 @@ pub type TestError = Error; pub type VaultRegistryError = vault_registry::Error; pub const USER: AccountId = 1; +pub const NOMINATOR1: AccountId = 11; +pub const NOMINATOR2: AccountId = 12; +pub const NOMINATOR_INIT_BALANCE: Balance = 10000; pub const VAULT: VaultId = VaultId { account_id: 2, currencies: VaultCurrencyPair { @@ -448,6 +457,12 @@ impl ExtBuilder { .assimilate_storage(&mut storage) .unwrap(); + frame_support::traits::GenesisBuild::::assimilate_storage( + &nomination::GenesisConfig { is_nomination_enabled: true }, + &mut storage, + ) + .unwrap(); + fee::GenesisConfig:: { issue_fee: UnsignedFixedPoint::checked_from_rational(5, 1000).unwrap(), // 0.5% issue_griefing_collateral: UnsignedFixedPoint::checked_from_rational(5, 1_000_000_000) @@ -522,6 +537,8 @@ impl ExtBuilder { (VAULT3.account_id, currency_id, BOB_BALANCE), (VAULT4.account_id, currency_id, BOB_BALANCE), (VAULT5.account_id, currency_id, BOB_BALANCE), + (NOMINATOR1, currency_id, NOMINATOR_INIT_BALANCE), + (NOMINATOR2, currency_id, NOMINATOR_INIT_BALANCE), ] }) .collect(), diff --git a/pallets/issue/src/tests.rs b/pallets/issue/src/tests.rs index 3e800652f..97f1ae9db 100644 --- a/pallets/issue/src/tests.rs +++ b/pallets/issue/src/tests.rs @@ -626,7 +626,9 @@ fn test_request_issue_reset_interval_and_succeeds_with_rate_limit() { } mod integration_tests { use super::*; + use orml_traits::MultiCurrency; use pooled_rewards::RewardsApi; + fn get_reward_for_vault(vault: &DefaultVaultId, reward_currency: CurrencyId) -> Balance { <::VaultRewards as RewardsApi< CurrencyId, @@ -637,6 +639,24 @@ mod integration_tests { .unwrap() } + fn get_balance(currency_id: CurrencyId, account: &AccountId) -> Balance { + as MultiCurrency>::free_balance( + currency_id, + account, + ) + } + fn nominate_vault(nominator: AccountId, vault: DefaultVaultId, amount: Balance) { + let origin = RuntimeOrigin::signed(vault.account_id); + + assert_ok!(>::opt_in_to_nomination( + origin.clone(), + vault.clone().currencies + )); + + let nominator_origin = RuntimeOrigin::signed(nominator); + assert_ok!(>::deposit_collateral(nominator_origin, vault, amount)); + } + fn register_vault_with_collateral(vault: &DefaultVaultId, collateral_amount: Balance) { let origin = RuntimeOrigin::signed(vault.account_id); @@ -689,7 +709,8 @@ mod integration_tests { //In these tests in particular we are testing that a given fee is distributed to //the pooled-reward pallet depending on the collateral. - //These tests WILL NOT test the distribution from pooled-reward to staking. + //These first 3 integration tests WILL NOT test the distribution from pooled-reward to staking. + //The last one DOES #[test] fn integration_single_vault_gets_fee_rewards_when_issuing() { run_test(|| { @@ -763,8 +784,77 @@ mod integration_tests { }) } + fn execute_multiple_issues() { + //execute the issue + let issue_amount = 3000; + let issue_fee = 1000; + let griefing_collateral = 1; + let amount_transferred = 3000; + let issue_id = + setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id)); + + //execute the issue on the other currency + let issue_amount2 = 3000; + let issue_fee2 = 500; + let griefing_collateral2 = 1; + let amount_transferred2 = 3000; + let issue_id2 = setup_execute_with_vault( + issue_amount2, + issue_fee2, + griefing_collateral2, + amount_transferred2, + VAULT2.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id2)); + + let issue_amount3 = 3000; + let issue_fee3 = 600; + let griefing_collateral3 = 1; + let amount_transferred3 = 3000; + let issue_id3 = setup_execute_with_vault( + issue_amount3, + issue_fee3, + griefing_collateral3, + amount_transferred3, + VAULT3.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id3)); + + let issue_amount4 = 3000; + let issue_fee4 = 400; + let griefing_collateral4 = 1; + let amount_transferred4 = 3000; + let issue_id4 = setup_execute_with_vault( + issue_amount4, + issue_fee4, + griefing_collateral4, + amount_transferred4, + VAULT4.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id4)); + + let issue_amount5 = 3000; + let issue_fee5 = 700; + let griefing_collateral5 = 1; + let amount_transferred5 = 3000; + let issue_id5 = setup_execute_with_vault( + issue_amount5, + issue_fee5, + griefing_collateral5, + amount_transferred5, + VAULT5.clone(), + ) + .unwrap(); + assert_ok!(execute_issue(USER, &issue_id5)); + } + #[test] - fn integration_multiple_vaults_many_collateral_gets_fee_rewards_when_issuing() { + fn integration_multiple_vaults_many_collateral() { run_test(|| { //set up the vaults let collateral1: u128 = 1000; @@ -782,95 +872,90 @@ mod integration_tests { let collateral5: u128 = 800; register_vault_with_collateral(&VAULT5, collateral5); - //execute the issue + //execute issues let issue_asset = VAULT.wrapped_currency(); - let issue_amount = 3000; - let issue_fee = 1000; - let griefing_collateral = 1; - let amount_transferred = 3000; - let issue_id = - setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) - .unwrap(); - assert_ok!(execute_issue(USER, &issue_id)); - - //execute the issue on the other currency let issue_asset2 = VAULT2.wrapped_currency(); - let issue_amount2 = 3000; - let issue_fee2 = 500; - let griefing_collateral2 = 1; - let amount_transferred2 = 3000; - let issue_id2 = setup_execute_with_vault( - issue_amount2, - issue_fee2, - griefing_collateral2, - amount_transferred2, - VAULT2.clone(), - ) - .unwrap(); - assert_ok!(execute_issue(USER, &issue_id2)); - let issue_asset3 = VAULT3.wrapped_currency(); - let issue_amount3 = 3000; - let issue_fee3 = 600; - let griefing_collateral3 = 1; - let amount_transferred3 = 3000; - let issue_id3 = setup_execute_with_vault( - issue_amount3, - issue_fee3, - griefing_collateral3, - amount_transferred3, - VAULT3.clone(), - ) - .unwrap(); - assert_ok!(execute_issue(USER, &issue_id3)); - - let _issue_asset4 = VAULT4.wrapped_currency(); - let issue_amount4 = 3000; - let issue_fee4 = 400; - let griefing_collateral4 = 1; - let amount_transferred4 = 3000; - let issue_id4 = setup_execute_with_vault( - issue_amount4, - issue_fee4, - griefing_collateral4, - amount_transferred4, - VAULT4.clone(), - ) - .unwrap(); - assert_ok!(execute_issue(USER, &issue_id4)); - - let _issue_asset5 = VAULT5.wrapped_currency(); - let issue_amount5 = 3000; - let issue_fee5 = 700; - let griefing_collateral5 = 1; - let amount_transferred5 = 3000; - let issue_id5 = setup_execute_with_vault( - issue_amount5, - issue_fee5, - griefing_collateral5, - amount_transferred5, - VAULT5.clone(), - ) - .unwrap(); - assert_ok!(execute_issue(USER, &issue_id5)); + execute_multiple_issues(); //ensure that the current rewards equal to fee //Example: we expect for reward(vault1, asset1) = floor( //floor((1000/3000)*floor( 1400*(3000*5/(3000*5+3200*10+800*15)) )) = 118 - //where asset1 = asset4 - assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 118); - //withouth floor(), should actually exactly be 237.25. In this case + //assertions for asset1 = asset4 + assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 118); + //NOTE: withouth floor(), should actually exactly be 237.25. In this case //it is 236. The discrepancy is due to two consecutive roundings assert_eq!(get_reward_for_vault(&VAULT2, issue_asset), 236); assert_eq!(get_reward_for_vault(&VAULT3, issue_asset), 355); assert_eq!(get_reward_for_vault(&VAULT4, issue_asset), 402); assert_eq!(get_reward_for_vault(&VAULT5, issue_asset), 284); + //assertions for issue asset2 = asset5 assert_eq!(get_reward_for_vault(&VAULT, issue_asset2), 101); - //assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 13); + assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 202); + assert_eq!(get_reward_for_vault(&VAULT3, issue_asset2), 304); + assert_eq!(get_reward_for_vault(&VAULT4, issue_asset2), 345); + assert_eq!(get_reward_for_vault(&VAULT5, issue_asset2), 243); + //assertions for issue asset3 assert_eq!(get_reward_for_vault(&VAULT, issue_asset3), 50); + assert_eq!(get_reward_for_vault(&VAULT2, issue_asset3), 101); + assert_eq!(get_reward_for_vault(&VAULT3, issue_asset3), 152); + assert_eq!(get_reward_for_vault(&VAULT4, issue_asset3), 172); + assert_eq!(get_reward_for_vault(&VAULT5, issue_asset3), 122); + }); + } + + //same issue and fee set up as previous test, but nominators are + //taken into account. + //only vault 1 is taken into account for testing nominator rewards + #[test] + fn integration_multiple_vaults_many_collateral_nominated() { + run_test(|| { + //set up the vaults + let collateral1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral1); + + let collateral2: u128 = 2000; + register_vault_with_collateral(&VAULT2, collateral2); + + let collateral3: u128 = 1500; + register_vault_with_collateral(&VAULT3, collateral3); + + let collateral4: u128 = 1700; + register_vault_with_collateral(&VAULT4, collateral4); + + let collateral5: u128 = 800; + register_vault_with_collateral(&VAULT5, collateral5); + + //nominate + let nominator_amount: u128 = 500; + + nominate_vault(NOMINATOR1, VAULT, nominator_amount); + + //execute issues + execute_multiple_issues(); + + //assertions for asset1 = asset4 + assert_eq!(get_reward_for_vault(&VAULT, VAULT.wrapped_currency()), 170); + + //assertions for issue asset2 = asset5 + assert_eq!(get_reward_for_vault(&VAULT, VAULT2.wrapped_currency()), 146); + + //assertions for issue asset3 + assert_eq!(get_reward_for_vault(&VAULT, VAULT3.wrapped_currency()), 72); + + //nominator reward withdraw + //NOMINATOR collateral = 500, total collateral for vault 1500 + //we expect balance to increase on asset1 = floor( 170*(500/1500) ) + assert_ok!(>::collect_reward( + RuntimeOrigin::signed(NOMINATOR1.clone()).into(), + VAULT, + VAULT.wrapped_currency(), + None, + )); + assert_eq!(get_balance(VAULT.wrapped_currency(), &NOMINATOR1), 56); }) } } diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 8d3b278bf..fd1f015b0 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -173,18 +173,19 @@ pub mod pallet { &vault_id, reward_currency_id, )?; + ext::staking::distribute_reward::(&vault_id, reward, reward_currency_id)?; //withdraw the reward for specific nominator - let rewards = + let caller_rewards = ext::staking::withdraw_reward::(&vault_id, &caller, index, reward_currency_id)?; - if rewards == (BalanceOf::::zero()) { + if caller_rewards == (BalanceOf::::zero()) { return Err(Error::::NoRewardsForAccount.into()) } //transfer rewards - Self::transfer_reward(reward_currency_id, reward, caller) + Self::transfer_reward(reward_currency_id, caller_rewards, caller) } } } From 3ab1a2bb81f0074b7b68307cd3bb2dc7c85c4a14 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 11:44:47 -0300 Subject: [PATCH 40/52] change currency id back to associated type --- pallets/fee/src/lib.rs | 2 +- pallets/pooled-rewards/src/lib.rs | 39 +++++++++++++++----------- pallets/reward-distribution/src/lib.rs | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index d64d31fe7..eda4b3ccc 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -107,7 +107,7 @@ pub mod pallet { CurrencyId, DefaultVaultId, BalanceOf, - CurrencyId, + CurrencyId = CurrencyId, >; /// Pooled rewards distribution Interface diff --git a/pallets/pooled-rewards/src/lib.rs b/pallets/pooled-rewards/src/lib.rs index 5bf4f367e..fe03979e5 100644 --- a/pallets/pooled-rewards/src/lib.rs +++ b/pallets/pooled-rewards/src/lib.rs @@ -381,18 +381,18 @@ impl, I: 'static> Pallet { } } -pub trait RewardsApi +pub trait RewardsApi where Balance: Saturating + PartialOrd + Copy, { - //type PoolRewardsCurrencyId = CurrencyId; + type CurrencyId; fn reward_currencies_len(pool_id: &PoolId) -> u32; /// Distribute the `amount` to all participants OR error if zero total stake. fn distribute_reward( pool_id: &PoolId, - currency_id: CurrencyId, + currency_id: Self::CurrencyId, amount: Balance, ) -> DispatchResult; @@ -400,14 +400,14 @@ where fn compute_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: CurrencyId, + currency_id: Self::CurrencyId, ) -> Result; /// Withdraw all rewards from the `stake_id`. fn withdraw_reward( pool_id: &PoolId, stake_id: &StakeId, - currency_id: CurrencyId, + currency_id: Self::CurrencyId, ) -> Result; /// Deposit stake for an account. @@ -447,15 +447,14 @@ where fn get_total_stake_all_pools() -> Result, DispatchError>; } -impl RewardsApi - for Pallet +impl RewardsApi for Pallet where T: Config, I: 'static, Balance: BalanceToFixedPoint> + Saturating + PartialOrd + Copy, ::Inner: TryInto, { - //type PoolRewardsCurrencyId = CurrencyId; + type CurrencyId = T::PoolRewardsCurrencyId; fn reward_currencies_len(pool_id: &T::PoolId) -> u32 { RewardCurrencies::::get(pool_id).len() as u32 @@ -463,7 +462,7 @@ where fn distribute_reward( pool_id: &T::PoolId, - currency_id: T::PoolRewardsCurrencyId, + currency_id: Self::CurrencyId, amount: Balance, ) -> DispatchResult { Pallet::::distribute_reward( @@ -476,7 +475,7 @@ where fn compute_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::PoolRewardsCurrencyId, + currency_id: Self::CurrencyId, ) -> Result { Pallet::::compute_reward(pool_id, stake_id, currency_id)? .try_into() @@ -486,7 +485,7 @@ where fn withdraw_reward( pool_id: &T::PoolId, stake_id: &T::StakeId, - currency_id: T::PoolRewardsCurrencyId, + currency_id: Self::CurrencyId, ) -> Result { Pallet::::withdraw_reward(pool_id, stake_id, currency_id)? .try_into() @@ -548,25 +547,33 @@ where } } -impl RewardsApi for () +impl RewardsApi for () where Balance: Saturating + PartialOrd + Default + Copy, { - //type PoolRewardsCurrencyId = (); + type CurrencyId = (); fn reward_currencies_len(_: &PoolId) -> u32 { Default::default() } - fn distribute_reward(_: &PoolId, _: CurrencyId, _: Balance) -> DispatchResult { + fn distribute_reward(_: &PoolId, _: Self::CurrencyId, _: Balance) -> DispatchResult { Ok(()) } - fn compute_reward(_: &PoolId, _: &StakeId, _: CurrencyId) -> Result { + fn compute_reward( + _: &PoolId, + _: &StakeId, + _: Self::CurrencyId, + ) -> Result { Ok(Default::default()) } - fn withdraw_reward(_: &PoolId, _: &StakeId, _: CurrencyId) -> Result { + fn withdraw_reward( + _: &PoolId, + _: &StakeId, + _: Self::CurrencyId, + ) -> Result { Ok(Default::default()) } diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index e99a55eb9..256cc6797 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -74,7 +74,7 @@ pub mod pallet { CurrencyId, DefaultVaultId, BalanceOf, - CurrencyId, + CurrencyId = CurrencyId, >; /// Vault staking pool. From 4f3f0ba0462dc9f3156b9393a534000249ee9906 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 12:20:03 -0300 Subject: [PATCH 41/52] get the relevant block numbers in reward-distribution pallet from security pallet --- pallets/reward-distribution/src/ext.rs | 4 ++++ pallets/reward-distribution/src/lib.rs | 8 ++++---- pallets/reward-distribution/src/tests.rs | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index f36fbe1f9..8bc08d144 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -16,6 +16,10 @@ pub(crate) mod security { ) -> Result { >::parachain_block_expired(opentime, period) } + + pub fn get_active_block() -> T::BlockNumber { + >::active_block_number() + } } #[cfg_attr(test, mockable)] diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 256cc6797..e4b8526e3 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -151,8 +151,7 @@ pub mod pallet { ensure_root(origin)?; RewardPerBlock::::put(new_reward_per_block); - RewardsAdaptedAt::::put(frame_system::Pallet::::block_number()); - + RewardsAdaptedAt::::put(ext::security::get_active_block::()); Self::deposit_event(Event::::RewardPerBlockAdapted(new_reward_per_block)); Ok(()) } @@ -223,7 +222,7 @@ impl Pallet { } } - pub fn execute_on_init(height: T::BlockNumber) { + pub fn execute_on_init(_height: T::BlockNumber) { //get reward per block let reward_per_block = match RewardPerBlock::::get() { Some(value) => value, @@ -256,7 +255,8 @@ impl Pallet { let decay_rate = T::DecayRate::get(); reward_this_block = (Perquintill::one() - decay_rate).mul_floor(reward_per_block); RewardPerBlock::::set(Some(reward_this_block)); - RewardsAdaptedAt::::set(Some(height)); + let active_block = ext::security::get_active_block::(); + RewardsAdaptedAt::::set(Some(active_block)); } } else { log::warn!("Failed to check if the parachain block expired"); diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index c70a58690..345ac7ee5 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -18,6 +18,8 @@ fn test_set_rewards_per_block() { run_test(|| { let new_rewards_per_block = 100; + ext::security::get_active_block::.mock_safe(move || MockResult::Return(1)); + assert_err!( RewardDistribution::set_reward_per_block( RuntimeOrigin::signed(1), @@ -79,6 +81,7 @@ fn on_initialize_hook_distribution_works() { let initial_stakes = build_total_stakes(); MockResult::Return(Ok(initial_stakes)) }); + ext::security::get_active_block::.mock_safe(move || MockResult::Return(1)); let mut expected_pool_ids = vec![XCM(0), XCM(1), XCM(2), XCM(3)].into_iter(); let mut expected_stake_per_pool = vec![46, 13, 34, 4].into_iter(); @@ -98,6 +101,8 @@ fn on_initialize_hook_distribution_works() { ); System::set_block_number(100); + ext::security::get_active_block::.mock_safe(move || MockResult::Return(100)); + let new_rewards_per_block = 100; assert_ok!(RewardDistribution::set_reward_per_block( RuntimeOrigin::root(), From 6f248b4f4b5deb0cd207c44718a5e5ce0000e7b3 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 12:40:08 -0300 Subject: [PATCH 42/52] optimization on distribute_rewards loop --- pallets/reward-distribution/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index e4b8526e3..4d0ef1ad6 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -276,14 +276,18 @@ impl Pallet { let total_stakes = ext::pooled_rewards::get_total_stake_all_pools::()?; let mut total_stake_in_usd = BalanceOf::::default(); + let mut stakes_in_usd = Vec::>::new(); for (currency_id, stake) in total_stakes.clone().into_iter() { let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; + stakes_in_usd.push(stake_in_usd); total_stake_in_usd = total_stake_in_usd.checked_add(&stake_in_usd).unwrap(); } //distribute the rewards to each collateral pool let mut error_reward_accum = BalanceOf::::zero(); - for (currency_id, stake) in total_stakes.into_iter() { - let stake_in_usd = T::OracleApi::currency_to_usd(&stake, ¤cy_id)?; + let mut stakes_in_usd_iter = stakes_in_usd.into_iter(); + for (currency_id, _stake) in total_stakes.into_iter() { + let stake_in_usd = + stakes_in_usd_iter.next().expect("cannot be less than previous iteration"); let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); let reward_for_pool = percentage.mul_floor(reward_amount); if ext::pooled_rewards::distribute_reward::( From beace26a25890297bf56c21d25fe00721f919bfb Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 13:07:18 -0300 Subject: [PATCH 43/52] change wrongly modified extrinsic indexes --- pallets/fee/src/lib.rs | 12 ++++++------ pallets/reward-distribution/src/lib.rs | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pallets/fee/src/lib.rs b/pallets/fee/src/lib.rs index eda4b3ccc..0e6ce40f3 100644 --- a/pallets/fee/src/lib.rs +++ b/pallets/fee/src/lib.rs @@ -241,7 +241,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(0)] + #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::set_issue_fee())] #[transactional] pub fn set_issue_fee( @@ -260,7 +260,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `griefing_collateral` - the new griefing collateral - #[pallet::call_index(1)] + #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::set_issue_griefing_collateral())] #[transactional] pub fn set_issue_griefing_collateral( @@ -282,7 +282,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(2)] + #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::set_redeem_fee())] #[transactional] pub fn set_redeem_fee( @@ -301,7 +301,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(3)] + #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::set_premium_redeem_fee())] #[transactional] pub fn set_premium_redeem_fee( @@ -320,7 +320,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `fee` - the new fee - #[pallet::call_index(4)] + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::set_punishment_fee())] #[transactional] pub fn set_punishment_fee( @@ -339,7 +339,7 @@ pub mod pallet { /// /// * `origin` - signing account /// * `griefing_collateral` - the new griefing collateral - #[pallet::call_index(5)] + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::set_replace_griefing_collateral())] #[transactional] pub fn set_replace_griefing_collateral( diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 4d0ef1ad6..9a724fa0e 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -21,6 +21,7 @@ use frame_support::{ use oracle::OracleApi; use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; use sp_runtime::traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}; +use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; From fd2804d1bbb7a952dcb6f7b3877aa738cd99c651 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 15:50:52 -0300 Subject: [PATCH 44/52] vault-registry integration tests for rewards cleanup --- pallets/issue/Cargo.toml | 3 +- pallets/issue/src/tests.rs | 1 - pallets/vault-registry/src/tests.rs | 381 +++++++++++++++++++--------- 3 files changed, 257 insertions(+), 128 deletions(-) diff --git a/pallets/issue/Cargo.toml b/pallets/issue/Cargo.toml index 3ca3aac97..6cd32235e 100644 --- a/pallets/issue/Cargo.toml +++ b/pallets/issue/Cargo.toml @@ -35,7 +35,7 @@ stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } reward-distribution = { path = "../reward-distribution", default-features = false } -nomination = {path = "../nomination", default-features = false} + primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -47,6 +47,7 @@ orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-li [dev-dependencies] mocktopus = "0.8.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40" } +nomination = {path = "../nomination", default-features = false} currency = { path = "../currency", features = ['testing-utils', 'testing-constants'] } reward = { path = "../reward" } diff --git a/pallets/issue/src/tests.rs b/pallets/issue/src/tests.rs index 97f1ae9db..160760edf 100644 --- a/pallets/issue/src/tests.rs +++ b/pallets/issue/src/tests.rs @@ -634,7 +634,6 @@ mod integration_tests { CurrencyId, DefaultVaultId, Balance, - CurrencyId, >>::compute_reward(&vault.collateral_currency(), vault, reward_currency) .unwrap() } diff --git a/pallets/vault-registry/src/tests.rs b/pallets/vault-registry/src/tests.rs index 1d732564f..66108ece3 100644 --- a/pallets/vault-registry/src/tests.rs +++ b/pallets/vault-registry/src/tests.rs @@ -1713,145 +1713,274 @@ fn test_offchain_worker_unsigned_transaction_submission() { }) } -#[test] -fn integration_single_vault_receives_per_block_reward() { - run_test(|| { - //set up reward values - let reward_per_block: u64 = 1000u64; - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); - //register vault and issue tokens. - let issue_tokens: u128 = DEFAULT_COLLATERAL / 10 / 2; // = 5 - let id = create_vault_and_issue_tokens(issue_tokens, DEFAULT_COLLATERAL, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(DEFAULT_COLLATERAL) - ); - assert_eq!(>::free_balance(&id.account_id), 0u128); +mod integration { + use super::{assert_eq, *}; + use oracle::OracleApi; - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); - assert_eq!( - >::free_balance(&id.account_id), - reward_per_block.into() - ); - }); -} + fn to_usd(amount: &Balance, currency: &CurrencyId) -> Balance { + ::OracleApi::currency_to_usd(amount, currency) + .expect("prices have been set in mock configuration") + } + #[test] + fn integration_single_vault_receives_per_block_reward() { + run_test(|| { + //set up reward values + let initial_block_number = 1u64; + let reward_per_block: u64 = 1000u64; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); + //register vault and issue tokens. + //the number of issue tokens is not relevant for these tests + let issue_tokens: u128 = 2; + let id = create_vault_and_issue_tokens(issue_tokens, DEFAULT_COLLATERAL, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id.collateral_currency(), &id), + Ok(DEFAULT_COLLATERAL) + ); + assert_eq!(>::free_balance(&id.account_id), 0u128); + + //distribute fee rewards + >::execute_on_init((initial_block_number + 1).into()); + //collect rewards + let origin = RuntimeOrigin::signed(id.account_id); + assert_ok!(>::collect_reward( + origin.into(), + id.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + assert_eq!( + >::free_balance(&id.account_id), + reward_per_block.into() + ); + }); + } -#[test] -fn integration_multiple_vault_same_collateral_per_block_reward() { - run_test(|| { - //set up reward values - let reward_per_block: u64 = 100000u64; - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); + #[test] + fn integration_multiple_vault_same_collateral_per_block_reward() { + run_test(|| { + //ARRANGE + //set up reward values + let initial_block_number = 1u64; + let reward_per_block: u128 = 100000u128; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); - //register vaults and issue tokens. - let issue_tokens: u128 = 5; + //register vaults and issue tokens. + let issue_tokens: u128 = 2; - let collateral_vault_1 = 1000u128; - let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(collateral_vault_1) - ); + let collateral_vault_1 = 1000u128; + let id_1 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_1.collateral_currency(), &id_1), + Ok(collateral_vault_1) + ); - let collateral_vault_2 = 5000u128; - let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); - assert_eq!( - ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), - Ok(collateral_vault_2) - ); + let collateral_vault_2 = 5000u128; + let id_2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_2.collateral_currency(), &id_2), + Ok(collateral_vault_2) + ); - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); + //ACT - distribute fee rewards + >::execute_on_init((initial_block_number + 1).into()); - //expected value = (1000*5/(5000*5 + 1000*5))*100000 = 16666 - assert_eq!(>::free_balance(&id.account_id), 16666u128.into()); - }); -} + //collect rewards for vault 1 and 2 + let origin_1 = RuntimeOrigin::signed(id_1.account_id); + assert_ok!(>::collect_reward( + origin_1.into(), + id_1.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); -#[test] -fn integration_multiple_vault_multiple_collateral_per_block_reward() { - run_test(|| { - //set up reward values and threshold - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - let reward_per_block: u64 = 100000u64; - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); + let origin_2 = RuntimeOrigin::signed(id_2.account_id); + assert_ok!(>::collect_reward( + origin_2.into(), + id_2.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); - //register vaults and issue tokens. - let issue_tokens: u128 = 5; + // ASSERT + //MODEL: reward_vault_i = ( usdPrice(vault_i_collateral)/ ( SUM_i { + // usdPrice(vault_i_collateral) })*reward_per_block + let vault_1_collateral_usd = to_usd(&collateral_vault_1, &DEFAULT_COLLATERAL_CURRENCY); + let vault_2_collateral_usd = to_usd(&collateral_vault_2, &DEFAULT_COLLATERAL_CURRENCY); - let collateral_vault_1 = 1000u128; - let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(collateral_vault_1) - ); + let expected_value_vault_1: u128 = ((vault_1_collateral_usd as f64 / + (vault_1_collateral_usd + vault_2_collateral_usd) as f64) * + reward_per_block as f64) + .floor() as u128; - let collateral_vault_2 = 5000u128; - let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); - assert_eq!( - ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), - Ok(collateral_vault_2) - ); + //collect rewards for vault 2 + let expected_value_vault_2: u128 = ((vault_2_collateral_usd as f64 / + (vault_1_collateral_usd + vault_2_collateral_usd) as f64) * + reward_per_block as f64) + .floor() as u128; - let collateral_vault_3 = 3000u128; - let id3 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_3, ID_COLLATERAL_21); - assert_eq!( - ::VaultRewards::get_stake(&id3.collateral_currency(), &id3), - Ok(collateral_vault_3) - ); + assert_eq!( + >::free_balance(&id_1.account_id), + expected_value_vault_1.into() + ); + assert_eq!( + >::free_balance(&id_2.account_id), + expected_value_vault_2.into() + ); + }); + } - let collateral_vault_4 = 2000u128; - let id4 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_4, ID_COLLATERAL_22); - assert_eq!( - ::VaultRewards::get_stake(&id4.collateral_currency(), &id4), - Ok(collateral_vault_4) - ); + #[test] + fn integration_multiple_vault_multiple_collateral_per_block_reward() { + run_test(|| { + //ARRANGE + //set up reward values and threshold + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + let reward_per_block: u128 = 100000; + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id4.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id4.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); + //register vaults and issue tokens. + let issue_tokens: u128 = 2; - //Explicit calc for the expected reward on this test for id4 - //expected value = ((2000*10 + 3000*10)/(5000*5 + 1000*5 + 2000*10 + - // 3000*10))*(2000/(3000+2000))*100000 = 25000 - assert_eq!( - >::free_balance(&id4.account_id), - 25000u128.into() - ); - }); + let collateral_vault_1 = 1000u128; + let id_1 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_1.collateral_currency(), &id_1), + Ok(collateral_vault_1) + ); + + let collateral_vault_2 = 5000u128; + let id_2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_2.collateral_currency(), &id_2), + Ok(collateral_vault_2) + ); + + let collateral_vault_3 = 3000u128; + let id_3 = + create_vault_and_issue_tokens(issue_tokens, collateral_vault_3, ID_COLLATERAL_21); + assert_eq!( + ::VaultRewards::get_stake(&id_3.collateral_currency(), &id_3), + Ok(collateral_vault_3) + ); + + let collateral_vault_4 = 2000u128; + let id_4 = + create_vault_and_issue_tokens(issue_tokens, collateral_vault_4, ID_COLLATERAL_22); + assert_eq!( + ::VaultRewards::get_stake(&id_4.collateral_currency(), &id_4), + Ok(collateral_vault_4) + ); + + //ACT + //distribute fee rewards + >::execute_on_init(2u32.into()); + + //collect rewards + let origin_1 = RuntimeOrigin::signed(id_1.account_id); + assert_ok!(>::collect_reward( + origin_1.into(), + id_1.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_2 = RuntimeOrigin::signed(id_2.account_id); + assert_ok!(>::collect_reward( + origin_2.into(), + id_2.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_3 = RuntimeOrigin::signed(id_3.account_id); + assert_ok!(>::collect_reward( + origin_3.into(), + id_3.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_4 = RuntimeOrigin::signed(id_4.account_id); + assert_ok!(>::collect_reward( + origin_4.into(), + id_4.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + //ASSERT + let vault_1_collateral_usd = + to_usd(&collateral_vault_1, &DEFAULT_ID.collateral_currency()); + let vault_2_collateral_usd = + to_usd(&collateral_vault_2, &OTHER_ID.collateral_currency()); + let vault_3_collateral_usd = + to_usd(&collateral_vault_3, &ID_COLLATERAL_21.collateral_currency()); + let vault_4_collateral_usd = + to_usd(&collateral_vault_4, &ID_COLLATERAL_22.collateral_currency()); + + let total_usd_amount = vault_1_collateral_usd + + vault_2_collateral_usd + + vault_3_collateral_usd + + vault_4_collateral_usd; + + let expected_value_vault_1: u128 = ((((vault_1_collateral_usd + vault_2_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_1 as f64 / + (collateral_vault_1 + collateral_vault_2) as f64)) + .floor() as u128; + + let expected_value_vault_2: u128 = ((((vault_1_collateral_usd + vault_2_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_2 as f64 / + (collateral_vault_1 + collateral_vault_2) as f64)) + .floor() as u128; + + let expected_value_vault_3: u128 = ((((vault_4_collateral_usd + vault_3_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_3 as f64 / + (collateral_vault_3 + collateral_vault_4) as f64)) + .floor() as u128; + + let expected_value_vault_4: u128 = ((((vault_4_collateral_usd + vault_3_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_4 as f64 / + (collateral_vault_3 + collateral_vault_4) as f64)) + .floor() as u128; + + assert_eq!( + >::free_balance(&id_1.account_id), + expected_value_vault_1.into() + ); + + assert_eq!( + >::free_balance(&id_2.account_id), + expected_value_vault_2.into() + ); + + assert_eq!( + >::free_balance(&id_3.account_id), + expected_value_vault_3.into() + ); + + assert_eq!( + >::free_balance(&id_4.account_id), + expected_value_vault_4.into() + ); + }); + } } From 2cf6582665b55ef588f7b5c29ad32b80f2bf35a5 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Mon, 23 Oct 2023 15:50:52 -0300 Subject: [PATCH 45/52] vault-registry integration tests for rewards cleanup --- pallets/issue/Cargo.toml | 3 +- pallets/issue/src/mock.rs | 56 ++-- pallets/issue/src/tests.rs | 447 ++++++++++++++++++++-------- pallets/vault-registry/src/tests.rs | 381 ++++++++++++++++-------- 4 files changed, 601 insertions(+), 286 deletions(-) diff --git a/pallets/issue/Cargo.toml b/pallets/issue/Cargo.toml index 3ca3aac97..6cd32235e 100644 --- a/pallets/issue/Cargo.toml +++ b/pallets/issue/Cargo.toml @@ -35,7 +35,7 @@ stellar-relay = { path = "../stellar-relay", default-features = false } vault-registry = { path = "../vault-registry", default-features = false } pooled-rewards = { path = "../pooled-rewards", default-features = false } reward-distribution = { path = "../reward-distribution", default-features = false } -nomination = {path = "../nomination", default-features = false} + primitives = { package = "spacewalk-primitives", path = "../../primitives", default-features = false } @@ -47,6 +47,7 @@ orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-li [dev-dependencies] mocktopus = "0.8.0" frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.40" } +nomination = {path = "../nomination", default-features = false} currency = { path = "../currency", features = ['testing-utils', 'testing-constants'] } reward = { path = "../reward" } diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index eb4473145..4c45b3c49 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -397,7 +397,7 @@ pub const VAULT: VaultId = VaultId { wrapped: DEFAULT_WRAPPED_CURRENCY, }, }; -pub const VAULT2: VaultId = VaultId { +pub const VAULT_2: VaultId = VaultId { account_id: 3, currencies: VaultCurrencyPair { collateral: DEFAULT_COLLATERAL_CURRENCY, @@ -405,17 +405,17 @@ pub const VAULT2: VaultId = VaultId { }, }; -pub const VAULT3: VaultId = VaultId { +pub const VAULT_3: VaultId = VaultId { account_id: 5, currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: WRAPPED_CURRENCY3 }, }; -pub const VAULT4: VaultId = VaultId { +pub const VAULT_4: VaultId = VaultId { account_id: 6, currencies: VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }, }; -pub const VAULT5: VaultId = VaultId { +pub const VAULT_5: VaultId = VaultId { account_id: 7, currencies: VaultCurrencyPair { collateral: XCM(2), wrapped: WRAPPED_CURRENCY2 }, }; @@ -424,13 +424,13 @@ const PAIR: VaultCurrencyPair = VaultCurrencyPair { collateral: DEFAULT_COLLATERAL_CURRENCY, wrapped: DEFAULT_WRAPPED_CURRENCY, }; -const PAIR2: VaultCurrencyPair = +const PAIR_2: VaultCurrencyPair = VaultCurrencyPair { collateral: DEFAULT_COLLATERAL_CURRENCY, wrapped: WRAPPED_CURRENCY2 }; -const PAIR3: VaultCurrencyPair = +const PAIR_3: VaultCurrencyPair = VaultCurrencyPair { collateral: XCM(1), wrapped: WRAPPED_CURRENCY3 }; -const PAIR4: VaultCurrencyPair = +const PAIR_4: VaultCurrencyPair = VaultCurrencyPair { collateral: XCM(1), wrapped: DEFAULT_WRAPPED_CURRENCY }; -const PAIR5: VaultCurrencyPair = +const PAIR_5: VaultCurrencyPair = VaultCurrencyPair { collateral: XCM(2), wrapped: WRAPPED_CURRENCY2 }; pub const ALICE_BALANCE: u128 = 1_000_000; @@ -492,31 +492,31 @@ impl ExtBuilder { punishment_delay: 8, system_collateral_ceiling: vec![ (PAIR, 1_000_000_000_000), - (PAIR2, 1_000_000_000_000), - (PAIR3, 1_000_000_000_000), - (PAIR4, 1_000_000_000_000), - (PAIR5, 1_000_000_000_000), + (PAIR_2, 1_000_000_000_000), + (PAIR_3, 1_000_000_000_000), + (PAIR_4, 1_000_000_000_000), + (PAIR_5, 1_000_000_000_000), ], secure_collateral_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), - (PAIR2, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), - (PAIR3, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), - (PAIR4, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), - (PAIR5, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR_2, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR_3, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR_4, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), + (PAIR_5, UnsignedFixedPoint::checked_from_rational(200, 100).unwrap()), ], premium_redeem_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), - (PAIR2, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), - (PAIR3, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), - (PAIR4, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), - (PAIR5, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR_2, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR_3, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR_4, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), + (PAIR_5, UnsignedFixedPoint::checked_from_rational(120, 100).unwrap()), ], liquidation_collateral_threshold: vec![ (PAIR, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), - (PAIR2, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), - (PAIR3, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), - (PAIR4, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), - (PAIR5, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR_2, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR_3, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR_4, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), + (PAIR_5, UnsignedFixedPoint::checked_from_rational(110, 100).unwrap()), ], } .assimilate_storage(&mut storage) @@ -533,10 +533,10 @@ impl ExtBuilder { vec![ (USER, currency_id, ALICE_BALANCE), (VAULT.account_id, currency_id, BOB_BALANCE), - (VAULT2.account_id, currency_id, BOB_BALANCE), - (VAULT3.account_id, currency_id, BOB_BALANCE), - (VAULT4.account_id, currency_id, BOB_BALANCE), - (VAULT5.account_id, currency_id, BOB_BALANCE), + (VAULT_2.account_id, currency_id, BOB_BALANCE), + (VAULT_3.account_id, currency_id, BOB_BALANCE), + (VAULT_4.account_id, currency_id, BOB_BALANCE), + (VAULT_5.account_id, currency_id, BOB_BALANCE), (NOMINATOR1, currency_id, NOMINATOR_INIT_BALANCE), (NOMINATOR2, currency_id, NOMINATOR_INIT_BALANCE), ] diff --git a/pallets/issue/src/tests.rs b/pallets/issue/src/tests.rs index 97f1ae9db..282dea6b6 100644 --- a/pallets/issue/src/tests.rs +++ b/pallets/issue/src/tests.rs @@ -626,6 +626,7 @@ fn test_request_issue_reset_interval_and_succeeds_with_rate_limit() { } mod integration_tests { use super::*; + use oracle::OracleApi; use orml_traits::MultiCurrency; use pooled_rewards::RewardsApi; @@ -634,7 +635,6 @@ mod integration_tests { CurrencyId, DefaultVaultId, Balance, - CurrencyId, >>::compute_reward(&vault.collateral_currency(), vault, reward_currency) .unwrap() } @@ -688,7 +688,7 @@ mod integration_tests { ext::fee::get_issue_fee::.mock_safe(move |_| { MockResult::Return(Ok(wrapped_with_custom_curr( issue_fee, - vault_clone_2.clone().wrapped_currency(), + vault_clone_2.wrapped_currency(), ))) }); ext::fee::get_issue_griefing_collateral:: @@ -707,6 +707,20 @@ mod integration_tests { Ok(issue_id) } + fn to_usd(amount: &Balance, currency: &CurrencyId) -> Balance { + ::OracleApi::currency_to_usd(amount, currency) + .expect("prices have been set in mock configuration") + } + + fn assert_approx_eq(a: Balance, b: Balance, epsilon: Balance) { + assert!( + (a as i128 - b as i128).abs() < epsilon as i128, + "Values are not approximately equal: {} != {}", + a, + b + ); + } + //In these tests in particular we are testing that a given fee is distributed to //the pooled-reward pallet depending on the collateral. //These first 3 integration tests WILL NOT test the distribution from pooled-reward to staking. @@ -738,172 +752,317 @@ mod integration_tests { #[test] fn integration_multiple_vaults_same_collateral_gets_fee_rewards_when_issuing() { run_test(|| { + //ARRANGE //set up the vaults - let collateral1: u128 = 1000; - register_vault_with_collateral(&VAULT, collateral1); + let collateral_1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral_1); - let collateral2: u128 = 2000; - register_vault_with_collateral(&VAULT2, collateral2); + let collateral_2: u128 = 2000; + register_vault_with_collateral(&VAULT_2, collateral_2); //execute the issue - let issue_asset = VAULT.wrapped_currency(); - let issue_amount = 30; - let issue_fee = 10; - let griefing_collateral = 1; - let amount_transferred = 30; - let issue_id = - setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) - .unwrap(); + let issue_asset_1 = VAULT.wrapped_currency(); + let issue_amount_1 = 30; + let issue_fee_1 = 10; + let griefing_collateral_1 = 1; + let amount_transferred_1 = 30; + let issue_id_1 = setup_execute( + issue_amount_1, + issue_fee_1, + griefing_collateral_1, + amount_transferred_1, + ) + .unwrap(); - assert_ok!(execute_issue(USER, &issue_id)); + //ACT + assert_ok!(execute_issue(USER, &issue_id_1)); //execute the issue on the other currency - let issue_asset2 = VAULT2.wrapped_currency(); - let issue_amount2 = 30; - let issue_fee2 = 20; - let griefing_collateral2 = 1; - let amount_transferred2 = 30; - let issue_id2 = setup_execute_with_vault( - issue_amount2, - issue_fee2, - griefing_collateral2, - amount_transferred2, - VAULT2.clone(), + let issue_asset_2 = VAULT_2.wrapped_currency(); + let issue_amount_2 = 30; + let issue_fee_2 = 20; + let griefing_collateral_2 = 1; + let amount_transferred_2 = 30; + let issue_id_2 = setup_execute_with_vault( + issue_amount_2, + issue_fee_2, + griefing_collateral_2, + amount_transferred_2, + VAULT_2.clone(), ) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id2)); + assert_ok!(execute_issue(USER, &issue_id_2)); + //ASSERT //ensure that the current rewards equal to fee //Example: we expect for reward(vault1, asset1) = floor( 10*(1000/3000) ) = 3 - assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 3); - assert_eq!(get_reward_for_vault(&VAULT2, issue_asset), 6); + let vault_1_collateral_usd = to_usd(&collateral_1, &VAULT.collateral_currency()); + let vault_2_collateral_usd = to_usd(&collateral_2, &VAULT_2.collateral_currency()); + let total_amount_usd = vault_1_collateral_usd + vault_2_collateral_usd; + + let expected_value_vault_1_fee_1: u128 = + ((vault_1_collateral_usd as f64 / total_amount_usd as f64) * issue_fee_1 as f64) + .floor() as u128; + + let expected_value_vault_2_fee_1: u128 = + ((vault_2_collateral_usd as f64 / total_amount_usd as f64) * issue_fee_1 as f64) + .floor() as u128; - assert_eq!(get_reward_for_vault(&VAULT, issue_asset2), 6); - assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 13); + let expected_value_vault_1_fee_2: u128 = + ((vault_1_collateral_usd as f64 / total_amount_usd as f64) * issue_fee_2 as f64) + .floor() as u128; + + let expected_value_vault_2_fee_2: u128 = + ((vault_2_collateral_usd as f64 / total_amount_usd as f64) * issue_fee_2 as f64) + .floor() as u128; + + assert_eq!(get_reward_for_vault(&VAULT, issue_asset_1), expected_value_vault_1_fee_1); + assert_eq!(get_reward_for_vault(&VAULT_2, issue_asset_1), expected_value_vault_2_fee_1); + + assert_eq!(get_reward_for_vault(&VAULT, issue_asset_2), expected_value_vault_1_fee_2); + assert_eq!(get_reward_for_vault(&VAULT_2, issue_asset_2), expected_value_vault_2_fee_2); }) } - fn execute_multiple_issues() { + fn execute_multiple_issues() -> (Balance, Balance, Balance) { //execute the issue - let issue_amount = 3000; - let issue_fee = 1000; - let griefing_collateral = 1; - let amount_transferred = 3000; - let issue_id = - setup_execute(issue_amount, issue_fee, griefing_collateral, amount_transferred) + let issue_amount_1 = 3000; + let issue_fee_1 = 1000; + let griefing_collateral_1 = 1; + let amount_transferred_1 = 3000; + let issue_id_1 = + setup_execute(issue_amount_1, issue_fee_1, griefing_collateral_1, amount_transferred_1) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id)); + assert_ok!(execute_issue(USER, &issue_id_1)); //execute the issue on the other currency - let issue_amount2 = 3000; - let issue_fee2 = 500; - let griefing_collateral2 = 1; - let amount_transferred2 = 3000; - let issue_id2 = setup_execute_with_vault( - issue_amount2, - issue_fee2, - griefing_collateral2, - amount_transferred2, - VAULT2.clone(), + let issue_amount_2 = 3000; + let issue_fee_2 = 500; + let griefing_collateral_2 = 1; + let amount_transferred_2 = 3000; + let issue_id_2 = setup_execute_with_vault( + issue_amount_2, + issue_fee_2, + griefing_collateral_2, + amount_transferred_2, + VAULT_2.clone(), ) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id2)); - - let issue_amount3 = 3000; - let issue_fee3 = 600; - let griefing_collateral3 = 1; - let amount_transferred3 = 3000; - let issue_id3 = setup_execute_with_vault( - issue_amount3, - issue_fee3, - griefing_collateral3, - amount_transferred3, - VAULT3.clone(), + assert_ok!(execute_issue(USER, &issue_id_2)); + + let issue_amount_3 = 3000; + let issue_fee_3 = 600; + let griefing_collateral_3 = 1; + let amount_transferred_3 = 3000; + let issue_id_3 = setup_execute_with_vault( + issue_amount_3, + issue_fee_3, + griefing_collateral_3, + amount_transferred_3, + VAULT_3.clone(), ) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id3)); - - let issue_amount4 = 3000; - let issue_fee4 = 400; - let griefing_collateral4 = 1; - let amount_transferred4 = 3000; - let issue_id4 = setup_execute_with_vault( - issue_amount4, - issue_fee4, - griefing_collateral4, - amount_transferred4, - VAULT4.clone(), + assert_ok!(execute_issue(USER, &issue_id_3)); + + let issue_amount_4 = 3000; + let issue_fee_4 = 400; + let griefing_collateral_4 = 1; + let amount_transferred_4 = 3000; + let issue_id_4 = setup_execute_with_vault( + issue_amount_4, + issue_fee_4, + griefing_collateral_4, + amount_transferred_4, + VAULT_4.clone(), ) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id4)); - - let issue_amount5 = 3000; - let issue_fee5 = 700; - let griefing_collateral5 = 1; - let amount_transferred5 = 3000; - let issue_id5 = setup_execute_with_vault( - issue_amount5, - issue_fee5, - griefing_collateral5, - amount_transferred5, - VAULT5.clone(), + assert_ok!(execute_issue(USER, &issue_id_4)); + + let issue_amount_5 = 3000; + let issue_fee_5 = 700; + let griefing_collateral_5 = 1; + let amount_transferred_5 = 3000; + let issue_id_5 = setup_execute_with_vault( + issue_amount_5, + issue_fee_5, + griefing_collateral_5, + amount_transferred_5, + VAULT_5.clone(), ) .unwrap(); - assert_ok!(execute_issue(USER, &issue_id5)); + assert_ok!(execute_issue(USER, &issue_id_5)); + + return (issue_fee_1 + issue_fee_4, issue_fee_2 + issue_fee_5, issue_fee_3) } #[test] fn integration_multiple_vaults_many_collateral() { run_test(|| { + //ARRAGE //set up the vaults - let collateral1: u128 = 1000; - register_vault_with_collateral(&VAULT, collateral1); + let collateral_1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral_1); - let collateral2: u128 = 2000; - register_vault_with_collateral(&VAULT2, collateral2); + let collateral_2: u128 = 2000; + register_vault_with_collateral(&VAULT_2, collateral_2); - let collateral3: u128 = 1500; - register_vault_with_collateral(&VAULT3, collateral3); + let collateral_3: u128 = 1500; + register_vault_with_collateral(&VAULT_3, collateral_3); - let collateral4: u128 = 1700; - register_vault_with_collateral(&VAULT4, collateral4); + let collateral_4: u128 = 1700; + register_vault_with_collateral(&VAULT_4, collateral_4); - let collateral5: u128 = 800; - register_vault_with_collateral(&VAULT5, collateral5); + let collateral_5: u128 = 800; + register_vault_with_collateral(&VAULT_5, collateral_5); //execute issues - let issue_asset = VAULT.wrapped_currency(); - let issue_asset2 = VAULT2.wrapped_currency(); - let issue_asset3 = VAULT3.wrapped_currency(); - execute_multiple_issues(); + let issue_asset_1 = VAULT.wrapped_currency(); + let issue_asset_2 = VAULT_2.wrapped_currency(); + let issue_asset_3 = VAULT_3.wrapped_currency(); + + //ACT + //get issue total for asset1, asset2 and asset3 + let (issue_fee_1, issue_fee_2, issue_fee_3) = execute_multiple_issues(); + //ASSERT //ensure that the current rewards equal to fee //Example: we expect for reward(vault1, asset1) = floor( //floor((1000/3000)*floor( 1400*(3000*5/(3000*5+3200*10+800*15)) )) = 118 + let vault_1_collateral_usd = to_usd(&collateral_1, &VAULT.collateral_currency()); + let vault_2_collateral_usd = to_usd(&collateral_2, &VAULT_2.collateral_currency()); + let vault_3_collateral_usd = to_usd(&collateral_3, &VAULT_3.collateral_currency()); + let vault_4_collateral_usd = to_usd(&collateral_4, &VAULT_4.collateral_currency()); + let vault_5_collateral_usd = to_usd(&collateral_5, &VAULT_5.collateral_currency()); + + let total_amount_usd = vault_1_collateral_usd + + vault_2_collateral_usd + + vault_3_collateral_usd + + vault_4_collateral_usd + + vault_5_collateral_usd; + + let get_expected_value_vault_1 = |fee: Balance| -> Balance { + ((((vault_1_collateral_usd + vault_2_collateral_usd) as f64 / + total_amount_usd as f64) * + fee as f64) + .floor() * (collateral_1 as f64 / (collateral_2 + collateral_1) as f64)) + .floor() as u128 + }; + + let get_expected_value_vault_2 = |fee: Balance| -> Balance { + ((((vault_1_collateral_usd + vault_2_collateral_usd) as f64 / + total_amount_usd as f64) * + fee as f64) + .floor() * (collateral_2 as f64 / (collateral_2 + collateral_1) as f64)) + .floor() as u128 + }; + + let get_expected_value_vault_3 = |fee: Balance| -> Balance { + ((((vault_3_collateral_usd + vault_4_collateral_usd) as f64 / + total_amount_usd as f64) * + fee as f64) + .floor() * (collateral_3 as f64 / (collateral_3 + collateral_4) as f64)) + .floor() as u128 + }; + + let get_expected_value_vault_4 = |fee: Balance| -> Balance { + ((((vault_3_collateral_usd + vault_4_collateral_usd) as f64 / + total_amount_usd as f64) * + fee as f64) + .floor() * (collateral_4 as f64 / (collateral_3 + collateral_4) as f64)) + .floor() as u128 + }; + + let get_expected_value_vault_5 = |fee: Balance| -> Balance { + (((vault_5_collateral_usd as f64 / total_amount_usd as f64) * fee as f64).floor()) + as u128 + }; + //assertions for asset1 = asset4 - assert_eq!(get_reward_for_vault(&VAULT, issue_asset), 118); - //NOTE: withouth floor(), should actually exactly be 237.25. In this case - //it is 236. The discrepancy is due to two consecutive roundings - assert_eq!(get_reward_for_vault(&VAULT2, issue_asset), 236); - assert_eq!(get_reward_for_vault(&VAULT3, issue_asset), 355); - assert_eq!(get_reward_for_vault(&VAULT4, issue_asset), 402); - assert_eq!(get_reward_for_vault(&VAULT5, issue_asset), 284); + assert_approx_eq( + get_reward_for_vault(&VAULT, issue_asset_1), + get_expected_value_vault_1(issue_fee_1), + 2, + ); + + assert_approx_eq( + get_reward_for_vault(&VAULT_2, issue_asset_1), + get_expected_value_vault_2(issue_fee_1), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_3, issue_asset_1), + get_expected_value_vault_3(issue_fee_1), + 2, + ); + //NOTE: withouth floor(), should actually exactly be 403.38. In this case + //it is 402. The discrepancy is due to two consecutive roundings, and also + //the precision by which the pallet pooled-rewards works. + assert_approx_eq( + get_reward_for_vault(&VAULT_4, issue_asset_1), + get_expected_value_vault_4(issue_fee_1), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_5, issue_asset_1), + get_expected_value_vault_5(issue_fee_1), + 2, + ); //assertions for issue asset2 = asset5 - assert_eq!(get_reward_for_vault(&VAULT, issue_asset2), 101); - assert_eq!(get_reward_for_vault(&VAULT2, issue_asset2), 202); - assert_eq!(get_reward_for_vault(&VAULT3, issue_asset2), 304); - assert_eq!(get_reward_for_vault(&VAULT4, issue_asset2), 345); - assert_eq!(get_reward_for_vault(&VAULT5, issue_asset2), 243); + assert_approx_eq( + get_reward_for_vault(&VAULT, issue_asset_2), + get_expected_value_vault_1(issue_fee_2), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_2, issue_asset_2), + get_expected_value_vault_2(issue_fee_2), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_3, issue_asset_2), + get_expected_value_vault_3(issue_fee_2), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_4, issue_asset_2), + get_expected_value_vault_4(issue_fee_2), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_5, issue_asset_2), + get_expected_value_vault_5(issue_fee_2), + 2, + ); //assertions for issue asset3 - assert_eq!(get_reward_for_vault(&VAULT, issue_asset3), 50); - assert_eq!(get_reward_for_vault(&VAULT2, issue_asset3), 101); - assert_eq!(get_reward_for_vault(&VAULT3, issue_asset3), 152); - assert_eq!(get_reward_for_vault(&VAULT4, issue_asset3), 172); - assert_eq!(get_reward_for_vault(&VAULT5, issue_asset3), 122); + assert_approx_eq( + get_reward_for_vault(&VAULT, issue_asset_3), + get_expected_value_vault_1(issue_fee_3), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_2, issue_asset_3), + get_expected_value_vault_2(issue_fee_3), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_3, issue_asset_3), + get_expected_value_vault_3(issue_fee_3), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_4, issue_asset_3), + get_expected_value_vault_4(issue_fee_3), + 2, + ); + assert_approx_eq( + get_reward_for_vault(&VAULT_5, issue_asset_3), + get_expected_value_vault_5(issue_fee_3), + 2, + ); }); } @@ -914,20 +1073,20 @@ mod integration_tests { fn integration_multiple_vaults_many_collateral_nominated() { run_test(|| { //set up the vaults - let collateral1: u128 = 1000; - register_vault_with_collateral(&VAULT, collateral1); + let collateral_1: u128 = 1000; + register_vault_with_collateral(&VAULT, collateral_1); - let collateral2: u128 = 2000; - register_vault_with_collateral(&VAULT2, collateral2); + let collateral_2: u128 = 2000; + register_vault_with_collateral(&VAULT_2, collateral_2); - let collateral3: u128 = 1500; - register_vault_with_collateral(&VAULT3, collateral3); + let collateral_3: u128 = 1500; + register_vault_with_collateral(&VAULT_3, collateral_3); - let collateral4: u128 = 1700; - register_vault_with_collateral(&VAULT4, collateral4); + let collateral_4: u128 = 1700; + register_vault_with_collateral(&VAULT_4, collateral_4); - let collateral5: u128 = 800; - register_vault_with_collateral(&VAULT5, collateral5); + let collateral_5: u128 = 800; + register_vault_with_collateral(&VAULT_5, collateral_5); //nominate let nominator_amount: u128 = 500; @@ -935,27 +1094,53 @@ mod integration_tests { nominate_vault(NOMINATOR1, VAULT, nominator_amount); //execute issues - execute_multiple_issues(); + let (issue_fee_1, _issue_fee_2, _issue_fee_3) = execute_multiple_issues(); //assertions for asset1 = asset4 assert_eq!(get_reward_for_vault(&VAULT, VAULT.wrapped_currency()), 170); //assertions for issue asset2 = asset5 - assert_eq!(get_reward_for_vault(&VAULT, VAULT2.wrapped_currency()), 146); + assert_eq!(get_reward_for_vault(&VAULT, VAULT_2.wrapped_currency()), 146); //assertions for issue asset3 - assert_eq!(get_reward_for_vault(&VAULT, VAULT3.wrapped_currency()), 72); + assert_eq!(get_reward_for_vault(&VAULT, VAULT_3.wrapped_currency()), 72); //nominator reward withdraw //NOMINATOR collateral = 500, total collateral for vault 1500 //we expect balance to increase on asset1 = floor( 170*(500/1500) ) + let vault_1_collateral_usd = to_usd(&collateral_1, &VAULT.collateral_currency()); + let nominator_amount_usd = to_usd(&nominator_amount, &VAULT.collateral_currency()); + let vault_2_collateral_usd = to_usd(&collateral_2, &VAULT_2.collateral_currency()); + let vault_3_collateral_usd = to_usd(&collateral_3, &VAULT_3.collateral_currency()); + let vault_4_collateral_usd = to_usd(&collateral_4, &VAULT_4.collateral_currency()); + let vault_5_collateral_usd = to_usd(&collateral_5, &VAULT_5.collateral_currency()); + + let total_amount_usd = vault_1_collateral_usd + + nominator_amount_usd + + vault_2_collateral_usd + + vault_3_collateral_usd + + vault_4_collateral_usd + + vault_5_collateral_usd; + + let reward_for_pool_1 = + ((((vault_1_collateral_usd + vault_2_collateral_usd + nominator_amount_usd) + as f64 / total_amount_usd as f64) * + issue_fee_1 as f64) + .floor() * ((collateral_1 + nominator_amount) as f64 / + (collateral_1 + collateral_2 + nominator_amount) as f64)) + .floor(); + + let reward_for_nominator = (reward_for_pool_1 * + (nominator_amount as f64 / (nominator_amount + collateral_1) as f64)) + as u128; + assert_ok!(>::collect_reward( RuntimeOrigin::signed(NOMINATOR1.clone()).into(), VAULT, VAULT.wrapped_currency(), None, )); - assert_eq!(get_balance(VAULT.wrapped_currency(), &NOMINATOR1), 56); + assert_eq!(get_balance(VAULT.wrapped_currency(), &NOMINATOR1), reward_for_nominator); }) } } diff --git a/pallets/vault-registry/src/tests.rs b/pallets/vault-registry/src/tests.rs index 1d732564f..66108ece3 100644 --- a/pallets/vault-registry/src/tests.rs +++ b/pallets/vault-registry/src/tests.rs @@ -1713,145 +1713,274 @@ fn test_offchain_worker_unsigned_transaction_submission() { }) } -#[test] -fn integration_single_vault_receives_per_block_reward() { - run_test(|| { - //set up reward values - let reward_per_block: u64 = 1000u64; - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); - //register vault and issue tokens. - let issue_tokens: u128 = DEFAULT_COLLATERAL / 10 / 2; // = 5 - let id = create_vault_and_issue_tokens(issue_tokens, DEFAULT_COLLATERAL, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(DEFAULT_COLLATERAL) - ); - assert_eq!(>::free_balance(&id.account_id), 0u128); +mod integration { + use super::{assert_eq, *}; + use oracle::OracleApi; - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); - assert_eq!( - >::free_balance(&id.account_id), - reward_per_block.into() - ); - }); -} + fn to_usd(amount: &Balance, currency: &CurrencyId) -> Balance { + ::OracleApi::currency_to_usd(amount, currency) + .expect("prices have been set in mock configuration") + } + #[test] + fn integration_single_vault_receives_per_block_reward() { + run_test(|| { + //set up reward values + let initial_block_number = 1u64; + let reward_per_block: u64 = 1000u64; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); + //register vault and issue tokens. + //the number of issue tokens is not relevant for these tests + let issue_tokens: u128 = 2; + let id = create_vault_and_issue_tokens(issue_tokens, DEFAULT_COLLATERAL, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id.collateral_currency(), &id), + Ok(DEFAULT_COLLATERAL) + ); + assert_eq!(>::free_balance(&id.account_id), 0u128); + + //distribute fee rewards + >::execute_on_init((initial_block_number + 1).into()); + //collect rewards + let origin = RuntimeOrigin::signed(id.account_id); + assert_ok!(>::collect_reward( + origin.into(), + id.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + assert_eq!( + >::free_balance(&id.account_id), + reward_per_block.into() + ); + }); + } -#[test] -fn integration_multiple_vault_same_collateral_per_block_reward() { - run_test(|| { - //set up reward values - let reward_per_block: u64 = 100000u64; - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); + #[test] + fn integration_multiple_vault_same_collateral_per_block_reward() { + run_test(|| { + //ARRANGE + //set up reward values + let initial_block_number = 1u64; + let reward_per_block: u128 = 100000u128; + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); - //register vaults and issue tokens. - let issue_tokens: u128 = 5; + //register vaults and issue tokens. + let issue_tokens: u128 = 2; - let collateral_vault_1 = 1000u128; - let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(collateral_vault_1) - ); + let collateral_vault_1 = 1000u128; + let id_1 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_1.collateral_currency(), &id_1), + Ok(collateral_vault_1) + ); - let collateral_vault_2 = 5000u128; - let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); - assert_eq!( - ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), - Ok(collateral_vault_2) - ); + let collateral_vault_2 = 5000u128; + let id_2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_2.collateral_currency(), &id_2), + Ok(collateral_vault_2) + ); - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); + //ACT - distribute fee rewards + >::execute_on_init((initial_block_number + 1).into()); - //expected value = (1000*5/(5000*5 + 1000*5))*100000 = 16666 - assert_eq!(>::free_balance(&id.account_id), 16666u128.into()); - }); -} + //collect rewards for vault 1 and 2 + let origin_1 = RuntimeOrigin::signed(id_1.account_id); + assert_ok!(>::collect_reward( + origin_1.into(), + id_1.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); -#[test] -fn integration_multiple_vault_multiple_collateral_per_block_reward() { - run_test(|| { - //set up reward values and threshold - assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); - let reward_per_block: u64 = 100000u64; - assert_ok!(>::set_reward_per_block( - RawOrigin::Root.into(), - reward_per_block.into() - )); + let origin_2 = RuntimeOrigin::signed(id_2.account_id); + assert_ok!(>::collect_reward( + origin_2.into(), + id_2.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); - //register vaults and issue tokens. - let issue_tokens: u128 = 5; + // ASSERT + //MODEL: reward_vault_i = ( usdPrice(vault_i_collateral)/ ( SUM_i { + // usdPrice(vault_i_collateral) })*reward_per_block + let vault_1_collateral_usd = to_usd(&collateral_vault_1, &DEFAULT_COLLATERAL_CURRENCY); + let vault_2_collateral_usd = to_usd(&collateral_vault_2, &DEFAULT_COLLATERAL_CURRENCY); - let collateral_vault_1 = 1000u128; - let id = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); - assert_eq!( - ::VaultRewards::get_stake(&id.collateral_currency(), &id), - Ok(collateral_vault_1) - ); + let expected_value_vault_1: u128 = ((vault_1_collateral_usd as f64 / + (vault_1_collateral_usd + vault_2_collateral_usd) as f64) * + reward_per_block as f64) + .floor() as u128; - let collateral_vault_2 = 5000u128; - let id2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); - assert_eq!( - ::VaultRewards::get_stake(&id2.collateral_currency(), &id2), - Ok(collateral_vault_2) - ); + //collect rewards for vault 2 + let expected_value_vault_2: u128 = ((vault_2_collateral_usd as f64 / + (vault_1_collateral_usd + vault_2_collateral_usd) as f64) * + reward_per_block as f64) + .floor() as u128; - let collateral_vault_3 = 3000u128; - let id3 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_3, ID_COLLATERAL_21); - assert_eq!( - ::VaultRewards::get_stake(&id3.collateral_currency(), &id3), - Ok(collateral_vault_3) - ); + assert_eq!( + >::free_balance(&id_1.account_id), + expected_value_vault_1.into() + ); + assert_eq!( + >::free_balance(&id_2.account_id), + expected_value_vault_2.into() + ); + }); + } - let collateral_vault_4 = 2000u128; - let id4 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_4, ID_COLLATERAL_22); - assert_eq!( - ::VaultRewards::get_stake(&id4.collateral_currency(), &id4), - Ok(collateral_vault_4) - ); + #[test] + fn integration_multiple_vault_multiple_collateral_per_block_reward() { + run_test(|| { + //ARRANGE + //set up reward values and threshold + assert_ok!(ext::staking::add_reward_currency::(DEFAULT_WRAPPED_CURRENCY)); + let reward_per_block: u128 = 100000; + assert_ok!(>::set_reward_per_block( + RawOrigin::Root.into(), + reward_per_block.into() + )); - //distribute fee rewards - >::execute_on_init(2u32.into()); - //collect rewards - let origin = RuntimeOrigin::signed(id4.account_id); - assert_ok!(>::collect_reward( - origin.into(), - id4.clone(), - DEFAULT_NATIVE_CURRENCY, - None, - )); + //register vaults and issue tokens. + let issue_tokens: u128 = 2; - //Explicit calc for the expected reward on this test for id4 - //expected value = ((2000*10 + 3000*10)/(5000*5 + 1000*5 + 2000*10 + - // 3000*10))*(2000/(3000+2000))*100000 = 25000 - assert_eq!( - >::free_balance(&id4.account_id), - 25000u128.into() - ); - }); + let collateral_vault_1 = 1000u128; + let id_1 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_1, DEFAULT_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_1.collateral_currency(), &id_1), + Ok(collateral_vault_1) + ); + + let collateral_vault_2 = 5000u128; + let id_2 = create_vault_and_issue_tokens(issue_tokens, collateral_vault_2, OTHER_ID); + assert_eq!( + ::VaultRewards::get_stake(&id_2.collateral_currency(), &id_2), + Ok(collateral_vault_2) + ); + + let collateral_vault_3 = 3000u128; + let id_3 = + create_vault_and_issue_tokens(issue_tokens, collateral_vault_3, ID_COLLATERAL_21); + assert_eq!( + ::VaultRewards::get_stake(&id_3.collateral_currency(), &id_3), + Ok(collateral_vault_3) + ); + + let collateral_vault_4 = 2000u128; + let id_4 = + create_vault_and_issue_tokens(issue_tokens, collateral_vault_4, ID_COLLATERAL_22); + assert_eq!( + ::VaultRewards::get_stake(&id_4.collateral_currency(), &id_4), + Ok(collateral_vault_4) + ); + + //ACT + //distribute fee rewards + >::execute_on_init(2u32.into()); + + //collect rewards + let origin_1 = RuntimeOrigin::signed(id_1.account_id); + assert_ok!(>::collect_reward( + origin_1.into(), + id_1.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_2 = RuntimeOrigin::signed(id_2.account_id); + assert_ok!(>::collect_reward( + origin_2.into(), + id_2.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_3 = RuntimeOrigin::signed(id_3.account_id); + assert_ok!(>::collect_reward( + origin_3.into(), + id_3.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + let origin_4 = RuntimeOrigin::signed(id_4.account_id); + assert_ok!(>::collect_reward( + origin_4.into(), + id_4.clone(), + DEFAULT_NATIVE_CURRENCY, + None, + )); + + //ASSERT + let vault_1_collateral_usd = + to_usd(&collateral_vault_1, &DEFAULT_ID.collateral_currency()); + let vault_2_collateral_usd = + to_usd(&collateral_vault_2, &OTHER_ID.collateral_currency()); + let vault_3_collateral_usd = + to_usd(&collateral_vault_3, &ID_COLLATERAL_21.collateral_currency()); + let vault_4_collateral_usd = + to_usd(&collateral_vault_4, &ID_COLLATERAL_22.collateral_currency()); + + let total_usd_amount = vault_1_collateral_usd + + vault_2_collateral_usd + + vault_3_collateral_usd + + vault_4_collateral_usd; + + let expected_value_vault_1: u128 = ((((vault_1_collateral_usd + vault_2_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_1 as f64 / + (collateral_vault_1 + collateral_vault_2) as f64)) + .floor() as u128; + + let expected_value_vault_2: u128 = ((((vault_1_collateral_usd + vault_2_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_2 as f64 / + (collateral_vault_1 + collateral_vault_2) as f64)) + .floor() as u128; + + let expected_value_vault_3: u128 = ((((vault_4_collateral_usd + vault_3_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_3 as f64 / + (collateral_vault_3 + collateral_vault_4) as f64)) + .floor() as u128; + + let expected_value_vault_4: u128 = ((((vault_4_collateral_usd + vault_3_collateral_usd) + as f64 / total_usd_amount as f64) * + reward_per_block as f64) + .floor() * (collateral_vault_4 as f64 / + (collateral_vault_3 + collateral_vault_4) as f64)) + .floor() as u128; + + assert_eq!( + >::free_balance(&id_1.account_id), + expected_value_vault_1.into() + ); + + assert_eq!( + >::free_balance(&id_2.account_id), + expected_value_vault_2.into() + ); + + assert_eq!( + >::free_balance(&id_3.account_id), + expected_value_vault_3.into() + ); + + assert_eq!( + >::free_balance(&id_4.account_id), + expected_value_vault_4.into() + ); + }); + } } From dd4f8edd119392a9dbebaecc92eda34165d4da43 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 24 Oct 2023 12:55:54 -0300 Subject: [PATCH 46/52] added pool staking manager interface --- pallets/nomination/src/ext.rs | 90 +++++++------------ pallets/nomination/src/lib.rs | 38 +++----- pallets/vault-registry/src/ext.rs | 24 +++-- pallets/vault-registry/src/lib.rs | 38 ++++---- .../src/pool_staking_manager.rs | 71 +++++++++++++++ pallets/vault-registry/src/types.rs | 31 ++++--- 6 files changed, 172 insertions(+), 120 deletions(-) create mode 100644 pallets/vault-registry/src/pool_staking_manager.rs diff --git a/pallets/nomination/src/ext.rs b/pallets/nomination/src/ext.rs index 5f0b58292..fb46cabde 100644 --- a/pallets/nomination/src/ext.rs +++ b/pallets/nomination/src/ext.rs @@ -12,6 +12,7 @@ pub(crate) mod security { #[cfg_attr(test, mockable)] pub(crate) mod vault_registry { + use crate::BalanceOf; use currency::Amount; pub use frame_support::dispatch::{DispatchError, DispatchResult}; pub use vault_registry::{types::CurrencyId, DefaultVault, VaultStatus}; @@ -60,23 +61,43 @@ pub(crate) mod vault_registry { ) -> DispatchResult { >::decrease_total_backing_collateral(currency_pair, amount) } -} -#[cfg_attr(test, mockable)] -pub(crate) mod reward_distribution { - use frame_support::pallet_prelude::DispatchResult; - use reward_distribution::DistributeRewards; - use vault_registry::DefaultVaultId; - pub fn withdraw_all_rewards_from_vault( - vault_id: &DefaultVaultId, - ) -> DispatchResult { - T::RewardDistribution::withdraw_all_rewards_from_vault(vault_id.clone()) + pub mod pool_manager { + use super::*; + + pub fn deposit_collateral( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + amount: &Amount, + ) -> Result<(), DispatchError> { + >::deposit_collateral(vault_id, nominator_id, amount) + } + + pub fn withdraw_collateral( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + maybe_amount: &Amount, + nonce: Option, + ) -> Result<(), DispatchError> { + >::withdraw_collateral( + vault_id, + nominator_id, + maybe_amount, + nonce, + ) + } + + pub fn kick_nominators( + vault_id: &DefaultVaultId, + ) -> Result, DispatchError> { + >::kick_nominators(vault_id) + } } } + #[cfg_attr(test, mockable)] pub(crate) mod staking { use crate::BalanceOf; - use currency::Amount; use frame_support::dispatch::DispatchError; use staking::Staking; use vault_registry::DefaultVaultId; @@ -85,57 +106,10 @@ pub(crate) mod staking { T::VaultStaking::nonce(vault_id) } - pub fn deposit_stake( - vault_id: &DefaultVaultId, - nominator_id: &T::AccountId, - amount: BalanceOf, - ) -> Result<(), DispatchError> { - T::VaultStaking::deposit_stake(vault_id, nominator_id, amount) - } - - pub fn withdraw_stake( - vault_id: &DefaultVaultId, - nominator_id: &T::AccountId, - amount: BalanceOf, - index: Option, - ) -> Result<(), DispatchError> { - T::VaultStaking::withdraw_stake(vault_id, nominator_id, amount, index) - } - pub fn compute_stake( vault_id: &DefaultVaultId, nominator_id: &T::AccountId, ) -> Result, DispatchError> { T::VaultStaking::compute_stake(vault_id, nominator_id) } - - pub fn force_refund( - vault_id: &DefaultVaultId, - ) -> Result, DispatchError> { - T::VaultStaking::force_refund(vault_id) - } - - pub fn total_current_stake_as_amount( - vault_id: &DefaultVaultId, - ) -> Result, DispatchError> { - let vault_total_stake = T::VaultStaking::total_stake(vault_id)?; - Ok(Amount::::new(vault_total_stake, vault_id.collateral_currency())) - } -} - -#[cfg_attr(test, mockable)] -pub(crate) mod pooled_rewards { - - use currency::Amount; - use frame_support::dispatch::DispatchError; - use pooled_rewards::RewardsApi; - - use crate::DefaultVaultId; - - pub fn set_stake( - vault_id: &DefaultVaultId, - amount: &Amount, - ) -> Result<(), DispatchError> { - T::VaultRewards::set_stake(&vault_id.collateral_currency(), &vault_id, amount.amount()) - } } diff --git a/pallets/nomination/src/lib.rs b/pallets/nomination/src/lib.rs index b25f8f744..d70ec3b30 100644 --- a/pallets/nomination/src/lib.rs +++ b/pallets/nomination/src/lib.rs @@ -241,18 +241,15 @@ impl Pallet { ext::vault_registry::decrease_total_backing_collateral(&vault_id.currencies, &amount)?; } - // withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; - // withdraw `amount` of stake from the vault staking pool - ext::staking::withdraw_stake::(vault_id, nominator_id, amount.amount(), Some(index))?; - + ext::vault_registry::pool_manager::withdraw_collateral::( + &vault_id, + &nominator_id, + &amount, + Some(index), + )?; amount.unlock_on(&vault_id.account_id)?; amount.transfer(&vault_id.account_id, nominator_id)?; - //decrease the stake in the reward pallet based on this updated stake - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; - Self::deposit_event(Event::::WithdrawCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), @@ -283,20 +280,16 @@ impl Pallet { Error::::DepositViolatesMaxNominationRatio ); - // Withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards - ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; - // Deposit `amount` of stake into the vault staking pool - ext::staking::deposit_stake::(vault_id, nominator_id, amount.amount())?; + ext::vault_registry::pool_manager::deposit_collateral::( + vault_id, + nominator_id, + &amount, + )?; + amount.transfer(nominator_id, &vault_id.account_id)?; amount.lock_on(&vault_id.account_id)?; ext::vault_registry::try_increase_total_backing_collateral(&vault_id.currencies, &amount)?; - //increase the stake in the reward pallet based on this updated stake - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - //TODO Perhaps we should cap it to "some" amount, to prevent - //extreme overcollateralization to just get rewards - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; - Self::deposit_event(Event::::DepositCollateral { vault_id: vault_id.clone(), nominator_id: nominator_id.clone(), @@ -329,7 +322,8 @@ impl Pallet { Error::::CollateralizationTooLow ); - let refunded_collateral = ext::staking::force_refund::(vault_id)?; + let refunded_collateral = + ext::vault_registry::pool_manager::kick_nominators::(vault_id)?; // Update the system-wide total backing collateral let vault_currency_id = vault_id.collateral_currency(); @@ -339,10 +333,6 @@ impl Pallet { &refunded_collateral, )?; - //update the reward pool - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; - >::remove(vault_id); Self::deposit_event(Event::::NominationOptOut { vault_id: vault_id.clone() }); Ok(()) diff --git a/pallets/vault-registry/src/ext.rs b/pallets/vault-registry/src/ext.rs index 34467e6aa..0685f5a45 100644 --- a/pallets/vault-registry/src/ext.rs +++ b/pallets/vault-registry/src/ext.rs @@ -50,8 +50,9 @@ pub(crate) mod staking { vault_id: &DefaultVaultId, nominator_id: &T::AccountId, amount: &Amount, - ) -> DispatchResult { - T::VaultStaking::withdraw_stake(vault_id, nominator_id, amount.amount(), None) + index: Option, + ) -> Result<(), DispatchError> { + T::VaultStaking::withdraw_stake(vault_id, nominator_id, amount.amount(), index) } pub fn slash_stake( @@ -74,11 +75,10 @@ pub(crate) mod staking { T::VaultStaking::total_stake(vault_id) } - pub fn total_current_stake_as_amount( + pub fn force_refund( vault_id: &DefaultVaultId, - ) -> Result, DispatchError> { - let vault_total_stake = T::VaultStaking::total_stake(vault_id)?; - Ok(Amount::::new(vault_total_stake, vault_id.collateral_currency())) + ) -> Result, DispatchError> { + T::VaultStaking::force_refund(vault_id) } pub fn add_reward_currency(currency: CurrencyId) -> DispatchResult { @@ -110,3 +110,15 @@ pub(crate) mod pooled_rewards { T::VaultRewards::get_stake(&vault_id.collateral_currency(), &vault_id) } } + +#[cfg_attr(test, mockable)] +pub(crate) mod reward_distribution { + use crate::DefaultVaultId; + use frame_support::pallet_prelude::DispatchResult; + use reward_distribution::DistributeRewards; + pub fn withdraw_all_rewards_from_vault( + vault_id: &DefaultVaultId, + ) -> DispatchResult { + T::RewardDistribution::withdraw_all_rewards_from_vault(vault_id.clone()) + } +} diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 530d4be79..9647f68cd 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -47,6 +47,9 @@ pub use crate::types::{ mod ext; pub mod types; +mod pool_staking_manager; +pub use pool_staking_manager::PoolManager; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -294,6 +297,7 @@ pub mod pallet { VaultId::new(account_id, currency_pair.collateral, currency_pair.wrapped); let mut vault = Self::get_active_rich_vault_from_id(&vault_id)?; vault.set_accept_new_issues(accept_new_issues)?; + PoolManager::::on_vault_settings_change(&vault_id)?; Ok(().into()) } @@ -916,11 +920,12 @@ impl Pallet { amount.lock_on(&vault_id.account_id)?; // Deposit `amount` of stake in the pool - ext::staking::deposit_stake::(vault_id, &vault_id.account_id, &amount.clone())?; + pool_staking_manager::PoolManager::deposit_collateral( + &vault_id, + &vault_id.account_id, + &amount.clone(), + )?; - //update staking reward - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; Ok(()) } @@ -938,11 +943,12 @@ impl Pallet { Self::decrease_total_backing_collateral(&vault_id.currencies, amount)?; // Withdraw `amount` of stake from the pool - ext::staking::withdraw_stake::(vault_id, &vault_id.account_id, &amount.clone())?; - - //update staking reward - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + pool_staking_manager::PoolManager::withdraw_collateral( + &vault_id, + &vault_id.account_id, + &amount, + None, + )?; Ok(()) } @@ -1019,11 +1025,9 @@ impl Pallet { ) -> DispatchResult { amount.unlock_on(&vault_id.account_id)?; Self::decrease_total_backing_collateral(&vault_id.currencies, amount)?; - ext::staking::slash_stake::(vault_id, amount)?; - //update staking reward - let new_total_stake = ext::staking::total_current_stake_as_amount::(vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + pool_staking_manager::PoolManager::slash_collateral(&vault_id, &amount)?; + Ok(()) } @@ -1498,15 +1502,11 @@ impl Pallet { old_vault.decrease_liquidated_collateral(&to_be_released)?; // deposit old-vault's collateral (this was withdrawn on liquidation) - ext::staking::deposit_stake::( - old_vault_id, + pool_staking_manager::PoolManager::deposit_collateral( + &old_vault_id, &old_vault_id.account_id, &to_be_released, )?; - - //update staking reward of new - let new_total_stake = ext::staking::total_current_stake_as_amount::(old_vault_id)?; - ext::pooled_rewards::set_stake::(&old_vault_id, &new_total_stake)?; } old_vault.execute_redeem_tokens(tokens)?; diff --git a/pallets/vault-registry/src/pool_staking_manager.rs b/pallets/vault-registry/src/pool_staking_manager.rs new file mode 100644 index 000000000..b607b8af8 --- /dev/null +++ b/pallets/vault-registry/src/pool_staking_manager.rs @@ -0,0 +1,71 @@ +use crate::*; +pub struct PoolManager(PhantomData); + +impl PoolManager { + pub fn deposit_collateral( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + amount: &Amount, + ) -> Result<(), DispatchError> { + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; + ext::staking::deposit_stake::(vault_id, nominator_id, amount)?; + + // also propagate to reward & capacity pools + Self::update_reward_stake(vault_id) + } + + pub fn withdraw_collateral( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + amount: &Amount, + nonce: Option, + ) -> Result<(), DispatchError> { + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; + ext::staking::withdraw_stake::(vault_id, nominator_id, amount, nonce)?; + + // also propagate to reward & capacity pools + Self::update_reward_stake(vault_id)?; + + Ok(()) + } + + pub fn slash_collateral( + vault_id: &DefaultVaultId, + amount: &Amount, + ) -> Result<(), DispatchError> { + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; + ext::staking::slash_stake(vault_id, amount)?; + + // also propagate to reward & capacity pools + Self::update_reward_stake(vault_id) + } + + pub fn kick_nominators(vault_id: &DefaultVaultId) -> Result, DispatchError> { + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; + let ret = ext::staking::force_refund::(vault_id)?; + + Self::update_reward_stake(vault_id)?; + + Ok(ret) + } + + pub fn on_vault_settings_change(vault_id: &DefaultVaultId) -> Result<(), DispatchError> { + ext::reward_distribution::withdraw_all_rewards_from_vault::(&vault_id)?; + Self::update_reward_stake(vault_id) + } + + pub(crate) fn update_reward_stake(vault_id: &DefaultVaultId) -> Result<(), DispatchError> { + let vault = Pallet::::get_vault_from_id(vault_id)?; + let new_reward_stake: currency::Amount = if !vault.accepts_new_issues() { + // if the vault is not accepting new issues it's not getting rewards + Amount::zero(vault_id.collateral_currency()) + } else { + let total_stake = ext::staking::total_current_stake::(vault_id)?; + Amount::new(total_stake, vault_id.collateral_currency()) + }; + + ext::pooled_rewards::set_stake(vault_id, &new_reward_stake)?; + + Ok(()) + } +} diff --git a/pallets/vault-registry/src/types.rs b/pallets/vault-registry/src/types.rs index e9982cab5..15e6d5b93 100644 --- a/pallets/vault-registry/src/types.rs +++ b/pallets/vault-registry/src/types.rs @@ -16,7 +16,7 @@ use sp_runtime::{ use currency::Amount; pub use primitives::{VaultCurrencyPair, VaultId}; -use crate::{ext, Config, Error, Pallet}; +use crate::{ext, pool_staking_manager, Config, Error, Pallet}; #[derive(Debug, PartialEq, Eq)] pub enum CurrencySource { @@ -191,6 +191,10 @@ impl< pub fn is_liquidated(&self) -> bool { matches!(self.status, VaultStatus::Liquidated) } + + pub fn accepts_new_issues(&self) -> bool { + matches!(self.status, VaultStatus::Active(true)) + } } pub type DefaultVault = Vault< @@ -535,11 +539,12 @@ impl RichVault { pub(crate) fn slash_for_to_be_redeemed(&mut self, amount: &Amount) -> DispatchResult { let vault_id = self.id(); let collateral = self.get_vault_collateral()?.min(amount)?; - ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &collateral.clone())?; - - let new_total_stake = ext::staking::total_current_stake_as_amount::(&vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; - + pool_staking_manager::PoolManager::withdraw_collateral( + &vault_id, + &vault_id.account_id, + &collateral, + None, + )?; self.increase_liquidated_collateral(&collateral)?; Ok(()) } @@ -555,15 +560,17 @@ impl RichVault { .unwrap_or((amount.clone(), None)); // "slash" vault first - ext::staking::withdraw_stake::(&vault_id, &vault_id.account_id, &to_withdraw.clone())?; + ext::staking::withdraw_stake::( + &vault_id, + &vault_id.account_id, + &to_withdraw.clone(), + None, + )?; // take remainder from nominators if let Some(to_slash) = to_slash { ext::staking::slash_stake::(&vault_id, &to_slash)?; - } - - let new_total_stake = ext::staking::total_current_stake_as_amount::(&vault_id)?; - ext::pooled_rewards::set_stake::(&vault_id, &new_total_stake)?; + }; Pallet::::transfer_funds( CurrencySource::LiquidatedCollateral(self.id()), @@ -620,8 +627,6 @@ impl RichVault { // todo: clear replace collateral? // withdraw stake from the reward pool - ext::pooled_rewards::set_stake::(&vault_id, &Amount::zero(vault_id.wrapped_currency()))?; - // Update vault: clear to_be_issued & issued_tokens, but don't touch to_be_redeemed let _ = self.update(|v| { v.to_be_issued_tokens = Zero::zero(); From 05b2bed97777144de080afcc1c3e5196feff911b Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 24 Oct 2023 13:47:25 -0300 Subject: [PATCH 47/52] native currency reward tracking and check --- pallets/reward-distribution/src/lib.rs | 24 +++++++++++++++++++++++- pallets/reward-distribution/src/tests.rs | 1 + pallets/vault-registry/src/types.rs | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 9a724fa0e..5117c5f30 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -119,6 +119,8 @@ pub mod pallet { Underflow, //if origin tries to withdraw with 0 rewards NoRewardsForAccount, + //error if amount to be minted is more than rewarded in total + NotEnoughRewardsRegistered, } #[pallet::hooks] @@ -139,6 +141,11 @@ pub mod pallet { #[pallet::getter(fn rewards_adapted_at)] pub(super) type RewardsAdaptedAt = StorageValue<_, BlockNumberFor, OptionQuery>; + //keeps track of the to-be-minted native rewards + #[pallet::storage] + #[pallet::getter(fn native_liability)] + pub(super) type NativeLiability = StorageValue<_, BalanceOf, OptionQuery>; + #[pallet::call] impl Pallet { /// Sets the reward per block. @@ -206,12 +213,23 @@ impl Pallet { return amount.transfer(&Self::fee_pool_account_id(), &beneficiary) }, Some(remaining) => { + //check if the to-be-minted amount is consistent + //with reward tracking + let liability = NativeLiability::::get() + .ok_or(Error::::NotEnoughRewardsRegistered)?; + + if liability < remaining { + return Err(Error::::NotEnoughRewardsRegistered.into()) + } + // Use the available funds from the fee pool let available_amount: currency::Amount = Amount::new(available_native_funds, reward_currency_id); available_amount.transfer(&Self::fee_pool_account_id(), &beneficiary)?; // Mint the rest T::Balances::deposit_creating(&beneficiary, remaining); + + NativeLiability::::set(Some(liability - remaining)); }, } Ok(()) @@ -262,7 +280,11 @@ impl Pallet { } else { log::warn!("Failed to check if the parachain block expired"); } - + //keep track of total native currencies to be minted + NativeLiability::::mutate(|current_liability| match current_liability { + Some(current_liability) => *current_liability += reward_this_block, + None => *current_liability = Some(reward_this_block), + }); //TODO how to handle error if on init cannot fail? let _ = Self::distribute_rewards(reward_this_block, T::GetNativeCurrencyId::get()); } diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index 345ac7ee5..4607a0b83 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -113,6 +113,7 @@ fn on_initialize_hook_distribution_works() { System::set_block_number(101); RewardDistribution::execute_on_init(101); + assert_eq!(RewardDistribution::native_liability(), Some(100)); }) } diff --git a/pallets/vault-registry/src/types.rs b/pallets/vault-registry/src/types.rs index 15e6d5b93..43e9b7ff6 100644 --- a/pallets/vault-registry/src/types.rs +++ b/pallets/vault-registry/src/types.rs @@ -560,7 +560,7 @@ impl RichVault { .unwrap_or((amount.clone(), None)); // "slash" vault first - ext::staking::withdraw_stake::( + pool_staking_manager::PoolManager::withdraw_collateral( &vault_id, &vault_id.account_id, &to_withdraw.clone(), From cb3cf1652001b5f2e8084cbb5c2a2ae98c6ffc32 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 25 Oct 2023 18:02:30 -0300 Subject: [PATCH 48/52] comments and fixes --- pallets/currency/src/testing_constants.rs | 23 ++++++++++++++ pallets/fee/src/mock.rs | 6 ++-- pallets/issue/src/mock.rs | 6 ++-- pallets/nomination/src/mock.rs | 6 ++-- pallets/nomination/src/tests.rs | 2 ++ pallets/oracle/src/lib.rs | 19 ++---------- pallets/oracle/src/oracle_api.rs | 16 ++++++++++ pallets/pooled-rewards/src/mock.rs | 24 +-------------- pallets/pooled-rewards/src/tests.rs | 30 +++++++++++++------ pallets/redeem/src/mock.rs | 6 ++-- pallets/replace/src/mock.rs | 6 ++-- .../reward-distribution/src/benchmarking.rs | 2 +- pallets/reward-distribution/src/lib.rs | 19 +++++++----- pallets/vault-registry/src/mock.rs | 6 ++-- 14 files changed, 96 insertions(+), 75 deletions(-) create mode 100644 pallets/oracle/src/oracle_api.rs diff --git a/pallets/currency/src/testing_constants.rs b/pallets/currency/src/testing_constants.rs index db087762c..6f9cd468e 100644 --- a/pallets/currency/src/testing_constants.rs +++ b/pallets/currency/src/testing_constants.rs @@ -14,6 +14,29 @@ pub const DEFAULT_WRAPPED_CURRENCY: CurrencyId = CurrencyId::AlphaNum4( ], ); +pub const DEFAULT_WRAPPED_CURRENCY_2: CurrencyId = CurrencyId::AlphaNum4( + *b"USDT", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); +pub const DEFAULT_WRAPPED_CURRENCY_3: CurrencyId = CurrencyId::AlphaNum4( + *b"MXN\0", + [ + 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, + 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, + ], +); + +pub const DEFAULT_WRAPPED_CURRENCY_4: CurrencyId = CurrencyId::AlphaNum4( + *b"ARST", + [ + 44, 123, 1, 49, 176, 55, 23, 123, 171, 123, 54, 155, 16, 50, 30, 226, 155, 231, 46, 199, 1, + 11, 4, 144, 240, 123, 51, 33, 72, 34, 159, 33, + ], +); + pub fn get_wrapped_currency_id() -> CurrencyId { DEFAULT_WRAPPED_CURRENCY } diff --git a/pallets/fee/src/mock.rs b/pallets/fee/src/mock.rs index f82d216e3..ec7afdb6b 100644 --- a/pallets/fee/src/mock.rs +++ b/pallets/fee/src/mock.rs @@ -238,10 +238,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } diff --git a/pallets/issue/src/mock.rs b/pallets/issue/src/mock.rs index caa605123..7e8fc679d 100644 --- a/pallets/issue/src/mock.rs +++ b/pallets/issue/src/mock.rs @@ -332,10 +332,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } diff --git a/pallets/nomination/src/mock.rs b/pallets/nomination/src/mock.rs index 2160c75b5..d8063e317 100644 --- a/pallets/nomination/src/mock.rs +++ b/pallets/nomination/src/mock.rs @@ -309,10 +309,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } diff --git a/pallets/nomination/src/tests.rs b/pallets/nomination/src/tests.rs index 6bbfc3930..4fff60526 100644 --- a/pallets/nomination/src/tests.rs +++ b/pallets/nomination/src/tests.rs @@ -19,6 +19,8 @@ fn collateral(amount: u128) -> Amount { #[test] fn should_deposit_against_valid_vault() { run_test(|| { + ext::vault_registry::pool_manager::deposit_collateral:: + .mock_safe(|_, _, _| MockResult::Return(Ok(()))); ext::vault_registry::vault_exists::.mock_safe(|_| MockResult::Return(true)); ext::vault_registry::get_backing_collateral:: .mock_safe(|_| MockResult::Return(Ok(collateral(10000)))); diff --git a/pallets/oracle/src/lib.rs b/pallets/oracle/src/lib.rs index fbb12b0c9..ae6362038 100644 --- a/pallets/oracle/src/lib.rs +++ b/pallets/oracle/src/lib.rs @@ -48,6 +48,9 @@ pub mod mock; pub mod types; pub mod dia; + +pub mod oracle_api; +pub use crate::oracle_api::*; #[cfg(feature = "testing-utils")] pub mod oracle_mock; @@ -367,19 +370,3 @@ impl Pallet { T::DataProvider::get_no_op(key) } } - -pub trait OracleApi { - fn currency_to_usd( - amount: &Balance, - currency_id: &CurrencyId, - ) -> Result; -} - -impl OracleApi, CurrencyId> for Pallet { - fn currency_to_usd( - amount: &BalanceOf, - currency_id: &CurrencyId, - ) -> Result, DispatchError> { - Pallet::::currency_to_usd(amount.clone(), currency_id.clone()) - } -} diff --git a/pallets/oracle/src/oracle_api.rs b/pallets/oracle/src/oracle_api.rs new file mode 100644 index 000000000..99a5c8a40 --- /dev/null +++ b/pallets/oracle/src/oracle_api.rs @@ -0,0 +1,16 @@ +use crate::{types::BalanceOf, Config, CurrencyId, DispatchError, Pallet}; +pub trait OracleApi { + fn currency_to_usd( + amount: &Balance, + currency_id: &CurrencyId, + ) -> Result; +} + +impl OracleApi, CurrencyId> for Pallet { + fn currency_to_usd( + amount: &BalanceOf, + currency_id: &CurrencyId, + ) -> Result, DispatchError> { + Pallet::::currency_to_usd(amount.clone(), currency_id.clone()) + } +} diff --git a/pallets/pooled-rewards/src/mock.rs b/pallets/pooled-rewards/src/mock.rs index cbe1908a3..74e0e34b4 100644 --- a/pallets/pooled-rewards/src/mock.rs +++ b/pallets/pooled-rewards/src/mock.rs @@ -8,6 +8,7 @@ use sp_runtime::{ pub use currency::testing_constants::{ DEFAULT_COLLATERAL_CURRENCY, DEFAULT_NATIVE_CURRENCY, DEFAULT_WRAPPED_CURRENCY, + DEFAULT_WRAPPED_CURRENCY_2, DEFAULT_WRAPPED_CURRENCY_3, DEFAULT_WRAPPED_CURRENCY_4, }; pub use primitives::{CurrencyId, VaultCurrencyPair, VaultId}; @@ -40,29 +41,6 @@ parameter_types! { pub const MaxRewardCurrencies: u32= 10; } -pub const DEFAULT_WRAPPED_CURRENCY2: CurrencyId = CurrencyId::AlphaNum4( - *b"USDT", - [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, - ], -); -pub const DEFAULT_WRAPPED_CURRENCY3: CurrencyId = CurrencyId::AlphaNum4( - *b"MXN\0", - [ - 20, 209, 150, 49, 176, 55, 23, 217, 171, 154, 54, 110, 16, 50, 30, 226, 102, 231, 46, 199, - 108, 171, 97, 144, 240, 161, 51, 109, 72, 34, 159, 139, - ], -); - -pub const DEFAULT_WRAPPED_CURRENCY4: CurrencyId = CurrencyId::AlphaNum4( - *b"ARST", - [ - 44, 123, 1, 49, 176, 55, 23, 123, 171, 123, 54, 155, 16, 50, 30, 226, 155, 231, 46, 199, 1, - 11, 4, 144, 240, 123, 51, 33, 72, 34, 159, 33, - ], -); - impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); diff --git a/pallets/pooled-rewards/src/tests.rs b/pallets/pooled-rewards/src/tests.rs index aa3d08b2d..89af98596 100644 --- a/pallets/pooled-rewards/src/tests.rs +++ b/pallets/pooled-rewards/src/tests.rs @@ -251,19 +251,19 @@ fn should_distribute_with_different_rewards() { assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); assert_ok!(Reward::distribute_reward( &DEFAULT_COLLATERAL_CURRENCY, - DEFAULT_WRAPPED_CURRENCY2, + DEFAULT_WRAPPED_CURRENCY_2, fixed!(1000) )); assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); assert_ok!(Reward::distribute_reward( &DEFAULT_COLLATERAL_CURRENCY, - DEFAULT_WRAPPED_CURRENCY3, + DEFAULT_WRAPPED_CURRENCY_3, fixed!(1000) )); assert_ok!(Reward::deposit_stake(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, fixed!(100))); assert_ok!(Reward::distribute_reward( &DEFAULT_COLLATERAL_CURRENCY, - DEFAULT_WRAPPED_CURRENCY4, + DEFAULT_WRAPPED_CURRENCY_4, fixed!(1000) )); @@ -276,20 +276,32 @@ fn should_distribute_with_different_rewards() { 1 ); assert_approx_eq!( - Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY2) - .unwrap(), + Reward::compute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + &ALICE, + DEFAULT_WRAPPED_CURRENCY_2 + ) + .unwrap(), 1000, 1 ); assert_approx_eq!( - Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY3) - .unwrap(), + Reward::compute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + &ALICE, + DEFAULT_WRAPPED_CURRENCY_3 + ) + .unwrap(), 1000, 1 ); assert_approx_eq!( - Reward::compute_reward(&DEFAULT_COLLATERAL_CURRENCY, &ALICE, DEFAULT_WRAPPED_CURRENCY4) - .unwrap(), + Reward::compute_reward( + &DEFAULT_COLLATERAL_CURRENCY, + &ALICE, + DEFAULT_WRAPPED_CURRENCY_4 + ) + .unwrap(), 1000, 1 ); diff --git a/pallets/redeem/src/mock.rs b/pallets/redeem/src/mock.rs index 743505809..e18e092fb 100644 --- a/pallets/redeem/src/mock.rs +++ b/pallets/redeem/src/mock.rs @@ -350,10 +350,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } diff --git a/pallets/replace/src/mock.rs b/pallets/replace/src/mock.rs index e15359895..b467b962d 100644 --- a/pallets/replace/src/mock.rs +++ b/pallets/replace/src/mock.rs @@ -347,10 +347,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } diff --git a/pallets/reward-distribution/src/benchmarking.rs b/pallets/reward-distribution/src/benchmarking.rs index 1ea4269e0..fa4785fa9 100644 --- a/pallets/reward-distribution/src/benchmarking.rs +++ b/pallets/reward-distribution/src/benchmarking.rs @@ -34,7 +34,7 @@ pub mod benchmarks { let nominator = account("Alice", 0, 0); let nominated_amount: u32 = 40000; let reward_to_distribute: u32 = 100; - + NativeLiability::::set(Some(10000u64.into())); //get the vault let vault_id: DefaultVaultId = DefaultVaultId::::new( account("Vault", 0, 0), diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 5117c5f30..51acc4514 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -7,6 +7,9 @@ mod default_weights; +#[cfg(test)] +extern crate mocktopus; + use crate::types::{BalanceOf, DefaultVaultId}; use codec::{FullCodec, MaxEncodedLen}; use core::fmt::Debug; @@ -113,13 +116,13 @@ pub mod pallet { #[pallet::error] pub enum Error { - //Overflow + /// Overflow Overflow, - //underflow + /// Underflow Underflow, - //if origin tries to withdraw with 0 rewards + /// Origin attempt to withdraw with 0 rewards NoRewardsForAccount, - //error if amount to be minted is more than rewarded in total + /// Amount to be minted is more than total rewarded NotEnoughRewardsRegistered, } @@ -127,7 +130,6 @@ pub mod pallet { impl Hooks for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { Self::execute_on_init(n); - //TODO benchmark this weight properly ::WeightInfo::on_initialize() } } @@ -254,7 +256,6 @@ impl Pallet { if let Err(_) = ext::security::ensure_parachain_status_running::() { return } - let rewards_adapted_at = match RewardsAdaptedAt::::get() { Some(value) => value, None => { @@ -285,8 +286,10 @@ impl Pallet { Some(current_liability) => *current_liability += reward_this_block, None => *current_liability = Some(reward_this_block), }); - //TODO how to handle error if on init cannot fail? - let _ = Self::distribute_rewards(reward_this_block, T::GetNativeCurrencyId::get()); + + if let Err(_) = Self::distribute_rewards(reward_this_block, T::GetNativeCurrencyId::get()) { + log::warn!("Rewards distribution failed"); + } } //fetch total stake (all), and calulate total usd stake in percentage across pools diff --git a/pallets/vault-registry/src/mock.rs b/pallets/vault-registry/src/mock.rs index c943e575c..9b64de0a8 100644 --- a/pallets/vault-registry/src/mock.rs +++ b/pallets/vault-registry/src/mock.rs @@ -264,10 +264,10 @@ impl oracle::OracleApi for OracleApiMock { _amount: &Balance, currency_id: &CurrencyId, ) -> Result { - let _native_currency = GetNativeCurrencyId::get(); + let native_currency = GetNativeCurrencyId::get(); match currency_id { - _native_currency => return Ok(100), - //_ => unimplemented!("unimplemented mock conversion for currency"), + id if *id == native_currency => Ok(100), + _ => Ok(500), } } } From 70f9a1ad160630f05a08f5823d8bcea9962c74b7 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Thu, 26 Oct 2023 15:55:32 -0300 Subject: [PATCH 49/52] review fixes and changes --- .../reward-distribution/src/benchmarking.rs | 16 +- pallets/reward-distribution/src/lib.rs | 62 +++-- pallets/reward-distribution/src/tests.rs | 256 ++++++++++++++++-- pallets/staking/src/lib.rs | 16 +- pallets/vault-registry/src/lib.rs | 4 + 5 files changed, 297 insertions(+), 57 deletions(-) diff --git a/pallets/reward-distribution/src/benchmarking.rs b/pallets/reward-distribution/src/benchmarking.rs index fa4785fa9..7a88f9117 100644 --- a/pallets/reward-distribution/src/benchmarking.rs +++ b/pallets/reward-distribution/src/benchmarking.rs @@ -42,20 +42,20 @@ pub mod benchmarks { collateral_currency, ); - let _stake = - T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()).unwrap(); + let _stake = T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()) + .expect("error at deposit stake"); let _reward_stake = T::VaultRewards::deposit_stake( &collateral_currency, &vault_id, nominated_amount.into(), ) - .unwrap(); + .expect("error at deposit stake into pool rewards"); let _distributed = T::VaultRewards::distribute_reward( &collateral_currency, native_currency_id.clone(), reward_to_distribute.clone().into(), ) - .unwrap(); + .expect("error at distribute rewards"); #[extrinsic_call] _(RawOrigin::Signed(nominator.clone()), vault_id, native_currency_id, None); @@ -75,7 +75,7 @@ pub mod benchmarks { RawOrigin::Root.into(), new_reward_per_block.clone().into(), ) - .unwrap(); + .expect("Could no set reward per block"); assert_eq!(RewardDistribution::::reward_per_block(), Some(new_reward_per_block.into())); //set the vault and nominate it @@ -85,14 +85,14 @@ pub mod benchmarks { collateral_currency, ); - let _stake = - T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()).unwrap(); + let _stake = T::VaultStaking::deposit_stake(&vault_id, &nominator, nominated_amount.into()) + .expect("error at deposit stake"); let _reward_stake = T::VaultRewards::deposit_stake( &collateral_currency, &vault_id, nominated_amount.into(), ) - .unwrap(); + .expect("error at deposit stake into pool rewards"); // `on_initialize` benchmark call #[block] diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 51acc4514..5fc600936 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -61,7 +61,7 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; - //balance + /// Balance Type type Balance: AtLeast32BitUnsigned + TypeInfo + FixedPointOperand @@ -74,6 +74,7 @@ pub mod pallet { + Clone + From; + /// Rewards API interface to connect with pooled-rewards type VaultRewards: pooled_rewards::RewardsApi< CurrencyId, DefaultVaultId, @@ -81,7 +82,7 @@ pub mod pallet { CurrencyId = CurrencyId, >; - /// Vault staking pool. + /// Rewards API interface to connect with staking pallet type VaultStaking: staking::Staking< DefaultVaultId, Self::AccountId, @@ -89,13 +90,16 @@ pub mod pallet { BalanceOf, CurrencyId, >; - + /// Maximum allowed currencies type MaxCurrencies: Get; + /// Fee pallet id from which fee account is derived type FeePalletId: Get; + /// Oracle API adaptor type OracleApi: oracle::OracleApi, CurrencyId>; + /// Currency trait adaptor type Balances: Currency>; /// Defines the interval (in number of blocks) at which the reward per block decays. @@ -124,6 +128,9 @@ pub mod pallet { NoRewardsForAccount, /// Amount to be minted is more than total rewarded NotEnoughRewardsRegistered, + /// If distribution logic reaches an inconsistency with the amount of currencies in the + /// system + InconsistentRewardCurrencies, } #[pallet::hooks] @@ -139,11 +146,12 @@ pub mod pallet { #[pallet::getter(fn reward_per_block)] pub type RewardPerBlock = StorageValue<_, BalanceOf, OptionQuery>; + /// Last Block were rewards per block were modified #[pallet::storage] #[pallet::getter(fn rewards_adapted_at)] pub(super) type RewardsAdaptedAt = StorageValue<_, BlockNumberFor, OptionQuery>; - //keeps track of the to-be-minted native rewards + /// Storage to keep track of the to-be-minted native rewards #[pallet::storage] #[pallet::getter(fn native_liability)] pub(super) type NativeLiability = StorageValue<_, BalanceOf, OptionQuery>; @@ -166,6 +174,7 @@ pub mod pallet { Ok(()) } + /// Allow users who have staked to collect rewards for a given vault and rewraded currency #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::collect_reward())] #[transactional] @@ -231,7 +240,9 @@ impl Pallet { // Mint the rest T::Balances::deposit_creating(&beneficiary, remaining); - NativeLiability::::set(Some(liability - remaining)); + NativeLiability::::set(Some( + liability.checked_sub(&remaining).ok_or(Error::::Underflow)?, + )); }, } Ok(()) @@ -244,6 +255,10 @@ impl Pallet { } pub fn execute_on_init(_height: T::BlockNumber) { + if let Err(_) = ext::security::ensure_parachain_status_running::() { + return + } + //get reward per block let reward_per_block = match RewardPerBlock::::get() { Some(value) => value, @@ -253,9 +268,6 @@ impl Pallet { }, }; - if let Err(_) = ext::security::ensure_parachain_status_running::() { - return - } let rewards_adapted_at = match RewardsAdaptedAt::::get() { Some(value) => value, None => { @@ -266,14 +278,27 @@ impl Pallet { let mut reward_this_block = reward_per_block; - //update the reward per block if decay interval passed - if let Ok(expired) = ext::security::parachain_block_expired::( - rewards_adapted_at, - T::DecayInterval::get() - T::BlockNumber::one(), - ) { + let decay_interval_sub = match T::DecayInterval::get().checked_sub(&T::BlockNumber::one()) { + Some(value) => value, + None => { + log::warn!("Decay interval underflow"); + return + }, + }; + + if let Ok(expired) = + ext::security::parachain_block_expired::(rewards_adapted_at, decay_interval_sub) + { if expired { let decay_rate = T::DecayRate::get(); - reward_this_block = (Perquintill::one() - decay_rate).mul_floor(reward_per_block); + match Perquintill::one().checked_sub(&decay_rate) { + Some(value) => reward_this_block = value.mul_floor(reward_per_block), + None => { + log::warn!("Failed to update reward_this_block due to underflow."); + return + }, + } + RewardPerBlock::::set(Some(reward_this_block)); let active_block = ext::security::get_active_block::(); RewardsAdaptedAt::::set(Some(active_block)); @@ -292,8 +317,8 @@ impl Pallet { } } - //fetch total stake (all), and calulate total usd stake in percentage across pools - //distribute the reward accoridigly + //fetch total stake (of each pool), and calculate total USD stake in percentage across pools + //distribute the reward accordingly fn distribute_rewards( reward_amount: BalanceOf, reward_currency: CurrencyId, @@ -313,7 +338,7 @@ impl Pallet { let mut stakes_in_usd_iter = stakes_in_usd.into_iter(); for (currency_id, _stake) in total_stakes.into_iter() { let stake_in_usd = - stakes_in_usd_iter.next().expect("cannot be less than previous iteration"); + stakes_in_usd_iter.next().ok_or(Error::::InconsistentRewardCurrencies)?; let percentage = Perquintill::from_rational(stake_in_usd, total_stake_in_usd); let reward_for_pool = percentage.mul_floor(reward_amount); if ext::pooled_rewards::distribute_reward::( @@ -332,8 +357,7 @@ impl Pallet { } fn withdraw_all_rewards_from_vault(vault_id: DefaultVaultId) -> DispatchResult { - let mut all_reward_currencies = ext::staking::get_all_reward_currencies::()?; - all_reward_currencies.push(T::GetNativeCurrencyId::get()); + let all_reward_currencies = ext::staking::get_all_reward_currencies::()?; for currency_id in all_reward_currencies { let reward = ext::pooled_rewards::withdraw_reward::( &vault_id.collateral_currency(), diff --git a/pallets/reward-distribution/src/tests.rs b/pallets/reward-distribution/src/tests.rs index 4607a0b83..648e03211 100644 --- a/pallets/reward-distribution/src/tests.rs +++ b/pallets/reward-distribution/src/tests.rs @@ -1,15 +1,54 @@ -use crate::{ext, mock::*, pallet}; -use frame_support::{assert_err, assert_ok}; +use crate::{ext, mock::*, pallet, DefaultVaultId, Error, NativeLiability}; +pub use currency::testing_constants::{DEFAULT_COLLATERAL_CURRENCY, DEFAULT_WRAPPED_CURRENCY}; +use frame_benchmarking::account; +use frame_support::{assert_err, assert_ok, traits::Get}; use mocktopus::mocking::*; -use sp_runtime::DispatchError::BadOrigin; - -pub use currency::testing_constants::DEFAULT_COLLATERAL_CURRENCY; +use oracle::OracleApi; use primitives::CurrencyId::XCM; +use sp_runtime::{traits::One, DispatchError::BadOrigin}; +use staking::Staking; +const COLLATERAL_POOL_1: u128 = 1000u128; +const COLLATERAL_POOL_2: u128 = 3000u128; +const COLLATERAL_POOL_3: u128 = 5000u128; +const COLLATERAL_POOL_4: u128 = 500u128; fn build_total_stakes( ) -> Vec<(::CurrencyId, ::Balance)> { //total in usd 215000 - vec![(DEFAULT_COLLATERAL_CURRENCY, 1000), (XCM(1), 3000), (XCM(2), 5000), (XCM(3), 500)] + vec![ + (DEFAULT_COLLATERAL_CURRENCY, COLLATERAL_POOL_1), + (XCM(1), COLLATERAL_POOL_2), + (XCM(2), COLLATERAL_POOL_3), + (XCM(3), COLLATERAL_POOL_4), + ] +} + +fn to_usd( + amount: &::Balance, + currency_id: &::CurrencyId, +) -> ::Balance { + OracleApiMock::currency_to_usd(amount, currency_id).expect("currency not found in oracle mock") +} + +fn expected_vault_rewards( + reward: ::Balance, +) -> Vec<::Balance> { + let total_collateral_in_usd = to_usd(&COLLATERAL_POOL_1, &DEFAULT_COLLATERAL_CURRENCY) + + to_usd(&COLLATERAL_POOL_2, &XCM(1)) + + to_usd(&COLLATERAL_POOL_3, &XCM(2)) + + to_usd(&COLLATERAL_POOL_4, &XCM(3)); + + let reward_pool_1 = (reward as f64 * + (to_usd(&COLLATERAL_POOL_1, &DEFAULT_COLLATERAL_CURRENCY) as f64) / + (total_collateral_in_usd as f64)) as u128; + let reward_pool_2 = (reward as f64 * (to_usd(&COLLATERAL_POOL_2, &XCM(1)) as f64) / + (total_collateral_in_usd as f64)) as u128; + let reward_pool_3 = (reward as f64 * (to_usd(&COLLATERAL_POOL_3, &XCM(2)) as f64) / + (total_collateral_in_usd as f64)) as u128; + let reward_pool_4 = (reward as f64 * (to_usd(&COLLATERAL_POOL_4, &XCM(3)) as f64) / + (total_collateral_in_usd as f64)) as u128; + + vec![reward_pool_1, reward_pool_2, reward_pool_3, reward_pool_4] } #[cfg(test)] @@ -50,13 +89,14 @@ fn test_set_rewards_per_block() { #[test] fn distributes_rewards_properly() { run_test(|| { + let reward = 100u128; ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { let initial_stakes = build_total_stakes(); MockResult::Return(Ok(initial_stakes)) }); let mut expected_pool_ids = vec![XCM(0), XCM(1), XCM(2), XCM(3)].into_iter(); - let mut expected_stake_per_pool = vec![46, 13, 34, 4].into_iter(); + let mut expected_stake_per_pool = expected_vault_rewards(reward).into_iter(); ext::pooled_rewards::distribute_reward::.mock_safe( move |pool_id, reward_currency, amount| { let expected_pool_id = expected_pool_ids.next().expect("More calls than expected"); @@ -69,7 +109,7 @@ fn distributes_rewards_properly() { }, ); - assert_ok!(RewardDistribution::distribute_rewards(100, XCM(18))); + assert_ok!(RewardDistribution::distribute_rewards(reward, XCM(18))); }); } @@ -77,6 +117,7 @@ fn distributes_rewards_properly() { #[test] fn on_initialize_hook_distribution_works() { run_test(|| { + let new_rewards_per_block = 100; ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { let initial_stakes = build_total_stakes(); MockResult::Return(Ok(initial_stakes)) @@ -84,7 +125,7 @@ fn on_initialize_hook_distribution_works() { ext::security::get_active_block::.mock_safe(move || MockResult::Return(1)); let mut expected_pool_ids = vec![XCM(0), XCM(1), XCM(2), XCM(3)].into_iter(); - let mut expected_stake_per_pool = vec![46, 13, 34, 4].into_iter(); + let mut expected_stake_per_pool = expected_vault_rewards(new_rewards_per_block).into_iter(); ext::pooled_rewards::distribute_reward::.mock_safe( move |pool_id, reward_currency, amount| { let expected_pool_id = expected_pool_ids.next().expect("More calls than expected"); @@ -99,11 +140,12 @@ fn on_initialize_hook_distribution_works() { MockResult::Return(Ok(())) }, ); + let block_number: ::BlockNumber = + ::DecayInterval::get(); - System::set_block_number(100); - ext::security::get_active_block::.mock_safe(move || MockResult::Return(100)); + System::set_block_number(block_number); + ext::security::get_active_block::.mock_safe(move || MockResult::Return(block_number)); - let new_rewards_per_block = 100; assert_ok!(RewardDistribution::set_reward_per_block( RuntimeOrigin::root(), new_rewards_per_block @@ -111,9 +153,12 @@ fn on_initialize_hook_distribution_works() { assert_eq!(RewardDistribution::rewards_adapted_at(), Some(100)); - System::set_block_number(101); - RewardDistribution::execute_on_init(101); - assert_eq!(RewardDistribution::native_liability(), Some(100)); + let new_block_number = block_number + ::BlockNumber::one(); + System::set_block_number(new_block_number); + ext::security::get_active_block:: + .mock_safe(move || MockResult::Return(new_block_number)); + RewardDistribution::execute_on_init(new_block_number); + assert_eq!(RewardDistribution::native_liability(), Some(new_rewards_per_block)); }) } @@ -147,30 +192,199 @@ fn udpate_reward_does_not_trigger_incorrectly() { #[test] fn udpate_reward_per_block_works() { run_test(|| { + let block_number: ::BlockNumber = + ::DecayInterval::get(); + ext::pooled_rewards::get_total_stake_all_pools::.mock_safe(move || { let initial_stakes = build_total_stakes(); MockResult::Return(Ok(initial_stakes)) }); + + //NOTE: setting this to always return true is sufficient to trigger the reward update + // changing the block numbers on this tests MAY NOT affect the execution ext::security::parachain_block_expired:: .mock_safe(move |_, _| MockResult::Return(Ok(true))); + ext::security::get_active_block::.mock_safe(move || MockResult::Return(block_number)); + let new_rewards_per_block = 10000; assert_ok!(RewardDistribution::set_reward_per_block( RuntimeOrigin::root(), new_rewards_per_block )); - System::set_block_number(200); - //Security::::set_active_block_number(200); - RewardDistribution::execute_on_init(200); + System::set_block_number(block_number); + RewardDistribution::execute_on_init(block_number); //we expect rewards to have fallen by DecayRate percent from initial assert_eq!(pallet::RewardPerBlock::::get(), Some(9500)); - System::set_block_number(300); - //Security::::set_active_block_number(300); - RewardDistribution::execute_on_init(300); + let next_block_number = block_number + block_number; + + System::set_block_number(next_block_number); + ext::security::get_active_block:: + .mock_safe(move || MockResult::Return(next_block_number)); + RewardDistribution::execute_on_init(next_block_number); //we expect reward to have fallen by DecayRate twice (compound) assert_eq!(pallet::RewardPerBlock::::get(), Some(9025)); }) } + +#[cfg(test)] +#[test] +fn collect_rewards_works_native() { + //initial values + run_test(|| { + use pooled_rewards::RewardsApi; + let collateral_currency = XCM(1); + let native_currency_id = ::GetNativeCurrencyId::get(); + let nominator = account("Alice", 0, 0); + let nominated_amount: u128 = 40000; + let rewards_per_block: u128 = 10000; + //set reward per block + assert_ok!(RewardDistribution::set_reward_per_block( + RuntimeOrigin::root(), + rewards_per_block + )); + + //get the vault + let vault_id: DefaultVaultId = DefaultVaultId::::new( + account("Vault", 0, 0), + collateral_currency, + collateral_currency, + ); + + assert_ok!( + <::VaultStaking as Staking<_, _, _, _, _>>::deposit_stake( + &vault_id, + &nominator, + nominated_amount + ) + ); + assert_ok!(<::VaultRewards as RewardsApi<_, _, _>>::deposit_stake( + &collateral_currency, + &vault_id, + nominated_amount + )); + RewardDistribution::execute_on_init(1); + + assert_eq!(NativeLiability::::get(), Some(rewards_per_block)); + + assert_ok!(pallet::Pallet::::collect_reward( + RuntimeOrigin::signed(nominator), + vault_id, + native_currency_id, + None + )); + + assert_eq!(Balances::free_balance(&nominator), rewards_per_block.into()); + + assert_eq!(NativeLiability::::get(), Some(0)); + }) +} + +#[cfg(test)] +#[test] +fn collect_rewards_works_wrapped() { + //initial values + + use orml_traits::MultiCurrency; + run_test(|| { + use pooled_rewards::RewardsApi; + let collateral_currency = XCM(1); + let nominator = account("Alice", 0, 0); + let nominated_amount: u128 = 40000; + let rewards: u128 = 10000; + let fee_account = RewardDistribution::fee_pool_account_id(); + //fund fee account with the proper rewards + assert_ok!( as MultiCurrency>::deposit( + DEFAULT_WRAPPED_CURRENCY, + &fee_account, + rewards + )); + //get the vault + let vault_id: DefaultVaultId = DefaultVaultId::::new( + account("Vault", 0, 0), + collateral_currency, + DEFAULT_WRAPPED_CURRENCY, + ); + + assert_ok!( + <::VaultStaking as Staking<_, _, _, _, _>>::deposit_stake( + &vault_id, + &nominator, + nominated_amount + ) + ); + assert_ok!(<::VaultRewards as RewardsApi<_, _, _>>::deposit_stake( + &collateral_currency, + &vault_id, + nominated_amount + )); + + assert_ok!(RewardDistribution::distribute_rewards(rewards, DEFAULT_WRAPPED_CURRENCY)); + + assert_ok!(pallet::Pallet::::collect_reward( + RuntimeOrigin::signed(nominator), + vault_id, + DEFAULT_WRAPPED_CURRENCY, + None + )); + + assert_eq!( + as MultiCurrency>::free_balance( + DEFAULT_WRAPPED_CURRENCY, + &nominator + ), + rewards + ); + }) +} + +#[cfg(test)] +#[test] +fn collect_rewards_fails_if_no_rewards_for_user() { + //initial values + + use frame_support::assert_noop; + run_test(|| { + use pooled_rewards::RewardsApi; + let collateral_currency = XCM(1); + let native_currency_id = ::GetNativeCurrencyId::get(); + let nominator = account("Alice", 0, 0); + let nominated_amount: u128 = 40000; + //we set this to a large value for testing, this would be set + //during distribution of rewards + NativeLiability::::set(Some(10000u64.into())); + + //get the vault + let vault_id: DefaultVaultId = DefaultVaultId::::new( + account("Vault", 0, 0), + collateral_currency, + collateral_currency, + ); + + assert_ok!( + <::VaultStaking as Staking<_, _, _, _, _>>::deposit_stake( + &vault_id, + &nominator, + nominated_amount + ) + ); + assert_ok!(<::VaultRewards as RewardsApi<_, _, _>>::deposit_stake( + &collateral_currency, + &vault_id, + nominated_amount + )); + + assert_noop!( + pallet::Pallet::::collect_reward( + RuntimeOrigin::signed(nominator), + vault_id, + native_currency_id, + None + ), + Error::::NoRewardsForAccount + ); + }) +} diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 40f959359..115190baf 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -111,7 +111,7 @@ pub mod pallet { /// The currency ID type. type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord; - // + /// Maximum reward currencies allowed type MaxRewardCurrencies: Get; } @@ -273,7 +273,7 @@ pub mod pallet { pub type Nonce = StorageMap<_, Blake2_128Concat, DefaultVaultId, T::Index, ValueQuery>; - // store with all the reward currencies in use + /// store with all the reward currencies in use #[pallet::storage] pub type RewardCurrencies = StorageValue<_, BoundedBTreeSet, ValueQuery>; @@ -408,8 +408,7 @@ impl Pallet { .ok_or(ArithmeticError::Overflow)?; Ok::<_, DispatchError>(()) })?; - let mut all_reward_currencies = Self::get_all_reward_currencies()?; - all_reward_currencies.push(T::GetNativeCurrencyId::get()); + let all_reward_currencies = Self::get_all_reward_currencies()?; for currency_id in all_reward_currencies { >::mutate( currency_id, @@ -488,8 +487,7 @@ impl Pallet { // A slash means reward per token is no longer representative of the rewards // since `amount * reward_per_token` will be lost from the system. As such, // replenish rewards by the amount of reward lost with this slash - let mut all_reward_currencies = Self::get_all_reward_currencies()?; - all_reward_currencies.push(T::GetNativeCurrencyId::get()); + let all_reward_currencies = Self::get_all_reward_currencies()?; for currency_id in all_reward_currencies { Self::increase_rewards( nonce, @@ -680,8 +678,7 @@ impl Pallet { Ok::<_, DispatchError>(()) })?; - let mut all_reward_currencies = Self::get_all_reward_currencies()?; - all_reward_currencies.push(T::GetNativeCurrencyId::get()); + let all_reward_currencies = Self::get_all_reward_currencies()?; for currency_id in all_reward_currencies { >::mutate( currency_id, @@ -802,7 +799,8 @@ impl Pallet { } pub fn get_all_reward_currencies() -> Result, DispatchError> { - let values = RewardCurrencies::::get().into_iter().collect::>(); + let mut values = RewardCurrencies::::get().into_iter().collect::>(); + values.push(T::GetNativeCurrencyId::get()); Ok(values) } } diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 9647f68cd..2d1e3adf9 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -397,6 +397,10 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; Self::_set_secure_collateral_threshold(currency_pair.clone(), threshold); + // We add each wrapped currency that has a secure threshold to the reward currencies of + // our staking pallet + // This will ensure that all rewards currencies are taking into account when + // collecting those rewards ext::staking::add_reward_currency::(currency_pair.wrapped)?; Ok(()) } From ae080e532865a060e914d0d05c9e81b88a61baa4 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 27 Oct 2023 10:22:50 -0300 Subject: [PATCH 50/52] existential deposit check --- pallets/reward-distribution/src/ext.rs | 8 ++++ pallets/reward-distribution/src/lib.rs | 65 +++++++++++++------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 8bc08d144..bbe61495e 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -78,6 +78,14 @@ pub(crate) mod staking { T::VaultStaking::withdraw_reward(vault_id, nominator_id, index, currency_id) } + pub fn compute_reward( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + currency_id: CurrencyId, + ) -> Result, DispatchError> { + T::VaultStaking::compute_reward(vault_id, nominator_id, currency_id) + } + pub fn get_all_reward_currencies() -> Result>, DispatchError> { T::VaultStaking::get_all_reward_currencies() diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 5b9910960..e6262eac5 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -22,8 +22,12 @@ use frame_support::{ transactional, PalletId, }; use oracle::OracleApi; +use orml_traits::GetByKey; use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; -use sp_runtime::traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}; +use sp_runtime::{ + traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}, + Saturating, +}; use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -131,6 +135,8 @@ pub mod pallet { /// If distribution logic reaches an inconsistency with the amount of currencies in the /// system InconsistentRewardCurrencies, + /// If amount to withdraw is less than existential deposit + CollectAmountTooSmall, } #[pallet::hooks] @@ -193,15 +199,24 @@ pub mod pallet { )?; ext::staking::distribute_reward::(&vault_id, reward, reward_currency_id)?; + // We check if the amount to transfer is greater than of the existential deposit to + // avoid potential losses of reward currency, in case currency is not native + let minimum_transfer_amount = + ::ExistentialDeposits::get(&reward_currency_id); + let expected_rewards = + ext::staking::compute_reward::(&vault_id, &caller, reward_currency_id.clone())?; + + if expected_rewards == (BalanceOf::::zero()) { + return Err(Error::::NoRewardsForAccount.into()) + } + if expected_rewards < minimum_transfer_amount { + return Err(Error::::CollectAmountTooSmall.into()) + } //withdraw the reward for specific nominator let caller_rewards = ext::staking::withdraw_reward::(&vault_id, &caller, index, reward_currency_id)?; - if caller_rewards == (BalanceOf::::zero()) { - return Err(Error::::NoRewardsForAccount.into()) - } - //transfer rewards Self::transfer_reward(reward_currency_id, caller_rewards, caller) } @@ -248,8 +263,8 @@ impl Pallet { } Ok(()) } else { - //we need no checking of available funds, since the fee pool will ALWAYS have enough - // collected fees of the wrapped currencies + // We need no checking of available funds, since the fee pool will ALWAYS have enough + // collected fees of the wrapped currencies. let amount: currency::Amount = Amount::new(reward, reward_currency_id); amount.transfer(&Self::fee_pool_account_id(), &beneficiary) } @@ -279,31 +294,17 @@ impl Pallet { let mut reward_this_block = reward_per_block; - let decay_interval_sub = match T::DecayInterval::get().checked_sub(&T::BlockNumber::one()) { - Some(value) => value, - None => { - log::warn!("Decay interval underflow"); - return - }, - }; - - if let Ok(expired) = - ext::security::parachain_block_expired::(rewards_adapted_at, decay_interval_sub) - { - if expired { - let decay_rate = T::DecayRate::get(); - match Perquintill::one().checked_sub(&decay_rate) { - Some(value) => reward_this_block = value.mul_floor(reward_per_block), - None => { - log::warn!("Failed to update reward_this_block due to underflow."); - return - }, - } - - RewardPerBlock::::set(Some(reward_this_block)); - let active_block = ext::security::get_active_block::(); - RewardsAdaptedAt::::set(Some(active_block)); - } + if Ok(true) == + ext::security::parachain_block_expired::( + rewards_adapted_at, + T::DecayInterval::get().saturating_sub(T::BlockNumber::one()), + ) { + let decay_rate = T::DecayRate::get(); + reward_this_block = + (Perquintill::one().saturating_sub(decay_rate)).mul_floor(reward_per_block); + RewardPerBlock::::set(Some(reward_this_block)); + let active_block = ext::security::get_active_block::(); + RewardsAdaptedAt::::set(Some(active_block)); } else { log::warn!("Failed to check if the parachain block expired"); } From cd448b06c857fd3c79a1ae422b6bffb1928c05bb Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Fri, 27 Oct 2023 10:27:54 -0300 Subject: [PATCH 51/52] existential deposit check in collect extrinsic --- pallets/reward-distribution/src/ext.rs | 8 ++++ pallets/reward-distribution/src/lib.rs | 59 ++++++++++++++------------ 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/pallets/reward-distribution/src/ext.rs b/pallets/reward-distribution/src/ext.rs index 8bc08d144..bbe61495e 100644 --- a/pallets/reward-distribution/src/ext.rs +++ b/pallets/reward-distribution/src/ext.rs @@ -78,6 +78,14 @@ pub(crate) mod staking { T::VaultStaking::withdraw_reward(vault_id, nominator_id, index, currency_id) } + pub fn compute_reward( + vault_id: &DefaultVaultId, + nominator_id: &T::AccountId, + currency_id: CurrencyId, + ) -> Result, DispatchError> { + T::VaultStaking::compute_reward(vault_id, nominator_id, currency_id) + } + pub fn get_all_reward_currencies() -> Result>, DispatchError> { T::VaultStaking::get_all_reward_currencies() diff --git a/pallets/reward-distribution/src/lib.rs b/pallets/reward-distribution/src/lib.rs index 5fc600936..f9616b6ff 100644 --- a/pallets/reward-distribution/src/lib.rs +++ b/pallets/reward-distribution/src/lib.rs @@ -22,8 +22,12 @@ use frame_support::{ transactional, PalletId, }; use oracle::OracleApi; +use orml_traits::GetByKey; use sp_arithmetic::{traits::AtLeast32BitUnsigned, FixedPointOperand, Perquintill}; -use sp_runtime::traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}; +use sp_runtime::{ + traits::{AccountIdConversion, CheckedAdd, CheckedSub, One, Zero}, + Saturating, +}; use sp_std::vec::Vec; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -131,6 +135,8 @@ pub mod pallet { /// If distribution logic reaches an inconsistency with the amount of currencies in the /// system InconsistentRewardCurrencies, + /// If the amount to collect is less than existential deposit + CollectAmountTooSmall, } #[pallet::hooks] @@ -193,6 +199,21 @@ pub mod pallet { )?; ext::staking::distribute_reward::(&vault_id, reward, reward_currency_id)?; + // We check if the amount to transfer is greater than of the existential deposit to + // avoid potential losses of reward currency, in case currency is not native + let minimum_transfer_amount = + ::ExistentialDeposits::get(&reward_currency_id); + let expected_rewards = + ext::staking::compute_reward::(&vault_id, &caller, reward_currency_id.clone())?; + + if expected_rewards == (BalanceOf::::zero()) { + return Err(Error::::NoRewardsForAccount.into()) + } + + if expected_rewards < minimum_transfer_amount { + return Err(Error::::CollectAmountTooSmall.into()) + } + //withdraw the reward for specific nominator let caller_rewards = ext::staking::withdraw_reward::(&vault_id, &caller, index, reward_currency_id)?; @@ -278,31 +299,17 @@ impl Pallet { let mut reward_this_block = reward_per_block; - let decay_interval_sub = match T::DecayInterval::get().checked_sub(&T::BlockNumber::one()) { - Some(value) => value, - None => { - log::warn!("Decay interval underflow"); - return - }, - }; - - if let Ok(expired) = - ext::security::parachain_block_expired::(rewards_adapted_at, decay_interval_sub) - { - if expired { - let decay_rate = T::DecayRate::get(); - match Perquintill::one().checked_sub(&decay_rate) { - Some(value) => reward_this_block = value.mul_floor(reward_per_block), - None => { - log::warn!("Failed to update reward_this_block due to underflow."); - return - }, - } - - RewardPerBlock::::set(Some(reward_this_block)); - let active_block = ext::security::get_active_block::(); - RewardsAdaptedAt::::set(Some(active_block)); - } + if Ok(true) == + ext::security::parachain_block_expired::( + rewards_adapted_at, + T::DecayInterval::get().saturating_sub(T::BlockNumber::one()), + ) { + let decay_rate = T::DecayRate::get(); + reward_this_block = + (Perquintill::one().saturating_sub(decay_rate)).mul_floor(reward_per_block); + RewardPerBlock::::set(Some(reward_this_block)); + let active_block = ext::security::get_active_block::(); + RewardsAdaptedAt::::set(Some(active_block)); } else { log::warn!("Failed to check if the parachain block expired"); } From 3b316b423fe6add9460691c9998e2b15d0b75afa Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Wed, 8 Nov 2023 17:28:28 -0300 Subject: [PATCH 52/52] clippy changes --- pallets/vault-registry/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/vault-registry/src/lib.rs b/pallets/vault-registry/src/lib.rs index 2d1e3adf9..8874fc1cc 100644 --- a/pallets/vault-registry/src/lib.rs +++ b/pallets/vault-registry/src/lib.rs @@ -1734,7 +1734,7 @@ impl Pallet { currency_pair: DefaultVaultCurrencyPair, threshold: UnsignedFixedPoint, ) { - SecureCollateralThreshold::::insert(currency_pair.clone(), threshold); + SecureCollateralThreshold::::insert(currency_pair, threshold); } pub fn _set_premium_redeem_threshold(