From 39520e4ad6d924cf676f9418a52ced7508da7abd Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Fri, 20 Aug 2021 18:29:58 +0800 Subject: [PATCH 01/12] :recycle: ($PALLET) Replace gain_avg from U64F64 to FixedU128 --- Cargo.lock | 1 - pallets/liquidity-mining/Cargo.toml | 2 -- pallets/liquidity-mining/src/lib.rs | 28 ++++++++-------- pallets/liquidity-mining/src/tests.rs | 48 ++++++++++++++++----------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74a72162a2..4ade37b523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,7 +725,6 @@ dependencies = [ "pallet-collective", "parity-scale-codec", "sp-core", - "substrate-fixed", ] [[package]] diff --git a/pallets/liquidity-mining/Cargo.toml b/pallets/liquidity-mining/Cargo.toml index 12fec2bed4..cdcf7a079c 100644 --- a/pallets/liquidity-mining/Cargo.toml +++ b/pallets/liquidity-mining/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" [dependencies] codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -substrate-fixed = { git = "https://github.com/encointer/substrate-fixed", default-features = false } frame-system = { version = "3.0.0", default-features = false } frame-support = { version = "3.0.0", default-features = false } node-primitives = { path = "../../node/primitives", default-features = false } @@ -25,7 +24,6 @@ orml-currencies = "0.4.1-dev" default = ["std"] std = [ "codec/std", - "substrate-fixed/std", "frame-system/std", "frame-support/std", "node-primitives/std", diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index ef758563df..ff0a4dbf72 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -21,7 +21,10 @@ use frame_support::{ pallet_prelude::*, - sp_runtime::traits::{SaturatedConversion, Saturating, Zero}, + sp_runtime::{ + traits::{SaturatedConversion, Saturating, Zero}, + FixedPointNumber, FixedU128, + }, sp_std::{ cmp::{max, min}, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, @@ -34,7 +37,6 @@ use frame_system::pallet_prelude::*; use node_primitives::{CurrencyId, CurrencyIdExt, LeasePeriod, ParaId, TokenInfo, TokenSymbol}; use orml_traits::{LockIdentifier, MultiCurrency, MultiLockableCurrency, MultiReservableCurrency}; pub use pallet::*; -use substrate_fixed::{traits::FromFixed, types::U64F64}; #[cfg(test)] mod mock; @@ -150,9 +152,9 @@ impl PoolInfo { let v_old = *gain_avg; let user_deposit: u128 = deposit_data.deposit.saturated_into(); - let amount = BalanceOf::::saturated_from(u128::from_fixed( - ((v_new - v_old) * user_deposit).floor(), - )); + let amount = BalanceOf::::saturated_from( + (v_new - v_old).saturating_mul_int(user_deposit), + ); // Update the claimed of the reward reward.claimed = reward.claimed.saturating_add(amount); @@ -213,13 +215,13 @@ pub struct DepositData { /// /// - Arg0: The average gain in pico by 1 pico deposited from the startup of the liquidity-pool /// - Arg1: The block number updated lastest - gain_avgs: BTreeMap, + gain_avgs: BTreeMap, update_b: BlockNumberFor, } impl DepositData { pub(crate) fn from_pool(pool: &PoolInfo) -> Self { - let mut gain_avgs = BTreeMap::::new(); + let mut gain_avgs = BTreeMap::::new(); for (rtoken, reward) in pool.rewards.iter() { gain_avgs.insert(*rtoken, reward.gain_avg); @@ -240,7 +242,7 @@ pub struct RewardData { claimed: BalanceOf, /// The average gain in pico by 1 pico deposited from the startup of the liquidity-pool, /// updated when anyone deposits to / redeems from / claims from the liquidity-pool. - gain_avg: U64F64, + gain_avg: FixedU128, } impl RewardData { @@ -257,16 +259,16 @@ impl RewardData { ensure!(per_block > T::MinimumRewardPerBlock::get(), Error::::InvalidRewardPerBlock); - Ok(RewardData { total, per_block, claimed: Zero::zero(), gain_avg: U64F64::from_num(0) }) + Ok(RewardData { total, per_block, claimed: Zero::zero(), gain_avg: 0.into() }) } - pub(crate) fn per_block_per_deposited(&self, deposited: BalanceOf) -> U64F64 { + pub(crate) fn per_block_per_deposited(&self, deposited: BalanceOf) -> FixedU128 { let per_block: u128 = self.per_block.saturated_into(); let deposit: u128 = deposited.saturated_into(); match deposit { - 0 => U64F64::from_num(0), - _ => U64F64::from_num(per_block) / deposit, + 0 => 0.into(), + _ => FixedU128::from((per_block, deposit)), } } @@ -283,7 +285,7 @@ impl RewardData { let b_prev = max(block_last_updated, block_startup); let b_past: u128 = (n - b_prev).saturated_into(); - let gain_avg_new = self.gain_avg + pbpd * b_past; + let gain_avg_new = self.gain_avg + pbpd * b_past.into(); self.gain_avg = gain_avg_new; } diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index b03ff93f9e..23389a9b4d 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -16,9 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use frame_support::{assert_noop, assert_ok, dispatch::DispatchError, traits::Hooks}; +use frame_support::{ + assert_noop, assert_ok, + dispatch::DispatchError, + sp_runtime::{FixedPointNumber, FixedU128}, + traits::Hooks, +}; use node_primitives::{Balance, CurrencyId, TokenSymbol}; -use substrate_fixed::{traits::FromFixed, types::U64F64}; use crate::{mock::*, Error, PoolId, PoolInfo, PoolState, PoolType, TotalPoolInfos}; @@ -649,8 +653,9 @@ fn deposit_to_pool_ongoing_with_init_deposit_should_work() { assert_ok!(LM::deposit(Some(USER_1).into(), 0, 1_000_000)); let per_block: Balance = REWARD_AMOUNT / DAYS as Balance; - let pbpd: U64F64 = U64F64::from_num(per_block) / (2 * 1_000_000); - let reward_to_user_1: Balance = u128::from_fixed(pbpd * 100 * 1_000_000); + let pbpd = FixedU128::from((per_block, 2 * 1_000_000)); + let reward_to_user_1 = + (pbpd * (100 * 1_000_000).into()).into_inner() / FixedU128::accuracy(); assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user_1); assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); @@ -702,8 +707,9 @@ fn double_deposit_to_pool_ongoing_in_diff_block_should_work() { assert_ok!(LM::deposit(Some(USER_2).into(), 0, 1_000_000)); let per_block = REWARD_AMOUNT / DAYS as Balance; - let pbpd = U64F64::from_num(per_block) / 2_000_000; - let reward_to_user_2: Balance = u128::from_fixed(pbpd * 100 * 1_000_000); + let pbpd = FixedU128::from((per_block, 2 * 1_000_000)); + let reward_to_user_2 = + (pbpd * (100 * 1_000_000).into()).into_inner() / FixedU128::accuracy(); assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, reward_to_user_2); assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); @@ -926,8 +932,8 @@ fn redeem_from_pool_ongoing_should_work() { run_to_block(100); let per_block = REWARD_AMOUNT / DAYS as Balance; - let pbpd = U64F64::from_num(per_block) / (2 * UNIT); - let rewarded: Balance = u128::from_fixed((pbpd * 100 * UNIT).floor()); + let pbpd = FixedU128::from((per_block, 2 * UNIT)); + let rewarded = (pbpd * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); @@ -989,8 +995,9 @@ fn redeem_from_pool_retired_should_work() { run_to_block(DAYS); let per_block = REWARD_AMOUNT / DAYS as Balance; - let pbpd = U64F64::from_num(per_block) / (2 * UNIT); - let rewarded: Balance = u128::from_fixed((pbpd * DAYS as Balance * UNIT).floor()); + let pbpd = FixedU128::from((per_block, 2 * UNIT)); + let rewarded = + (pbpd * (DAYS as Balance * UNIT).into()).into_inner() / FixedU128::accuracy(); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); @@ -1059,8 +1066,8 @@ fn double_redeem_from_pool_in_diff_state_should_work() { run_to_block(100); let per_block = REWARD_AMOUNT / DAYS as Balance; - let pbpd = U64F64::from_num(per_block) / (2 * UNIT); - let old_rewarded: Balance = u128::from_fixed((pbpd * 100 * UNIT).floor()); + let pbpd = FixedU128::from((per_block, 2 * UNIT)); + let old_rewarded = (pbpd * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); @@ -1092,9 +1099,10 @@ fn double_redeem_from_pool_in_diff_state_should_work() { // USER_2 didn't remember to redeem until the seventh day run_to_block(7 * DAYS); - let pbpd = U64F64::from_num(per_block) / MinimumDeposit::get(); - let new_rewarded: Balance = - u128::from_fixed((pbpd * (DAYS - 100) as Balance * MinimumDeposit::get()).floor()); + let pbpd = FixedU128::from((per_block, MinimumDeposit::get())); + let new_rewarded = (pbpd * ((DAYS - 100) as Balance * MinimumDeposit::get()).into()) + .into_inner() / + FixedU128::accuracy(); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); @@ -1259,12 +1267,11 @@ fn claim_from_pool_ongoing_should_work() { assert_ok!(LM::claim(Some(USER_1).into(), 0)); let per_block = REWARD_AMOUNT / DAYS as Balance; - let reserved = per_block * DAYS as Balance; let free = REWARD_AMOUNT - reserved; - let pbpd = U64F64::from_num(per_block) / (2 * UNIT); - let rewarded: Balance = u128::from_fixed((pbpd * 100 * UNIT).floor()); + let pbpd = FixedU128::from((per_block, 2 * UNIT)); + let rewarded: Balance = (pbpd * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, rewarded); assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); @@ -1314,8 +1321,9 @@ fn claim_from_pool_retired_should_work() { let reserved = per_block * DAYS as Balance; let free = REWARD_AMOUNT - reserved; - let pbpd = U64F64::from_num(per_block) / (2 * UNIT); - let rewarded: Balance = u128::from_fixed((pbpd * DAYS as Balance * UNIT).floor()); + let pbpd = FixedU128::from((per_block, 2 * UNIT)); + let rewarded: Balance = + (pbpd * (DAYS as Balance * UNIT).into()).into_inner() / FixedU128::accuracy(); assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, rewarded); assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); From 929f2f4cab0ce8728a02cb79e47440a09ae155e3 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Fri, 20 Aug 2021 18:35:34 +0800 Subject: [PATCH 02/12] :construction: ($PALLET) Move mutation to the end of extrinsic --- pallets/liquidity-mining/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index ff0a4dbf72..8eb27b2ec5 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -816,12 +816,14 @@ pub mod pallet { let reward = RewardData::new(total, duration)?; - // Reserve the reward - T::MultiCurrency::reserve(token, &creator, reward.total)?; - rewards.insert(token, reward); } + // Reserve rewards + for (token, reward) in rewards.iter() { + T::MultiCurrency::reserve(*token, &creator, reward.total)?; + } + // Construct the PoolInfo let pool_id = Self::next_pool_id(); let mining_pool = PoolInfo { From dd4b2e0dde54d64dc3c9c329cb818750c08fa13a Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Fri, 20 Aug 2021 19:40:01 +0800 Subject: [PATCH 03/12] :white_check_mark: ($PALLET) Add simple integration test --- pallets/liquidity-mining/src/tests.rs | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 23389a9b4d..d40d5e309a 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -1474,3 +1474,62 @@ fn double_claim_in_same_block_should_fail() { assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::TooShortBetweenTwoClaim); }); } + +#[test] +fn simple_integration_test() { + new_test_ext().execute_with(|| { + const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; + + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).free, DEPOSIT_AMOUNT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).frozen, UNIT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).reserved, 0); + + run_to_block(100); + + assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); + + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).free, DEPOSIT_AMOUNT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).frozen, UNIT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).reserved, 0); + + run_to_block(200); + + assert_ok!(LM::claim(Some(USER_1).into(), 0)); + assert_ok!(LM::claim(Some(USER_2).into(), 0)); + + let pbpd_1 = FixedU128::from((PER_BLOCK, UNIT)); + let reward_step_1 = (pbpd_1 * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); + + let pbpd_2 = FixedU128::from((PER_BLOCK, 2 * UNIT)); + let reward_step_2 = (pbpd_2 * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_step_1 + reward_step_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_step_1 + reward_step_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, reward_step_2); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, reward_step_2); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + }); +} From 528ba588c4b4745721f708e412ebd97a85eeff08 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 12:28:17 +0800 Subject: [PATCH 04/12] :sparkles: ($PALLET) Add extrinsic: force_retire_pool --- pallets/liquidity-mining/src/lib.rs | 72 ++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index 8eb27b2ec5..76f8247f3d 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -79,6 +79,8 @@ pub struct PoolInfo { state: PoolState, /// The block number when the liquidity-pool startup block_startup: Option>, + /// The block number when the liquidity-pool retired + block_retired: Option>, } impl PoolInfo { @@ -86,8 +88,11 @@ impl PoolInfo { pub(crate) fn try_update(mut self) -> Self { // When pool in `PoolState::Ongoing` or `PoolState::Retired` if let Some(block_startup) = self.block_startup { - let block_end = self.duration + block_startup; - let n = min(frame_system::Pallet::::block_number(), block_end); + let block_retired = match self.block_retired { + Some(block_retired) => block_retired, + None => self.duration + block_startup, + }; + let n = min(frame_system::Pallet::::block_number(), block_retired); for (_, reward) in self.rewards.iter_mut() { reward.update(self.deposit, block_startup, self.update_b, n); @@ -102,13 +107,17 @@ impl PoolInfo { /// Trying to change the state from `PoolState::Approved` to `PoolState::Ongoing` /// /// __NOTE__: Only called in the `Hook` - pub(crate) fn try_startup(mut self, pid: PoolId, n: BlockNumberFor) -> Self { + pub(crate) fn try_startup(mut self, n: BlockNumberFor) -> Self { if self.state == PoolState::Approved { if n >= self.after_block_to_start && self.deposit >= self.min_deposit_to_start { self.block_startup = Some(n); self.state = PoolState::Ongoing; - Pallet::::deposit_event(Event::PoolStarted(pid, self.r#type, self.trading_pair)); + Pallet::::deposit_event(Event::PoolStarted( + self.pool_id, + self.r#type, + self.trading_pair, + )); } } @@ -116,16 +125,18 @@ impl PoolInfo { } /// Trying to change the state from `PoolState::Ongoing` to `PoolState::Retired` - pub(crate) fn try_retire(mut self, pid: PoolId) -> Self { + pub(crate) fn try_retire(mut self) -> Self { if self.state == PoolState::Ongoing { let n = frame_system::Pallet::::block_number(); if let Some(block_startup) = self.block_startup { - if n >= block_startup + self.duration { + let block_retired = block_startup + self.duration; + if n >= block_retired { self.state = PoolState::Retired; + self.block_retired = Some(block_retired); Pallet::::deposit_event(Event::PoolRetired( - pid, + self.pool_id, self.r#type, self.trading_pair, )); @@ -140,12 +151,12 @@ impl PoolInfo { pub(crate) fn try_settle_and_transfer( &mut self, deposit_data: &mut DepositData, - pid: PoolId, user: AccountIdOf, ) -> Result<(), DispatchError> { let mut to_rewards = Vec::<(CurrencyId, BalanceOf)>::new(); - if self.state == PoolState::Ongoing || self.state == PoolState::Retired { + // The pool was startup before. + if let Some(_block_startup) = self.block_startup { for (rtoken, reward) in self.rewards.iter_mut() { let v_new = reward.gain_avg; if let Some(gain_avg) = deposit_data.gain_avgs.get(rtoken) { @@ -180,7 +191,7 @@ impl PoolInfo { } Pallet::::deposit_event(Event::UserClaimed( - pid, + self.pool_id, self.r#type, self.trading_pair, to_rewards, @@ -567,6 +578,32 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(1_000)] + pub fn force_retire_pool(origin: OriginFor, pid: PoolId) -> DispatchResultWithPostInfo { + let _ = T::ControlOrigin::ensure_origin(origin)?; + + let pool: PoolInfo = Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire(); + + ensure!( + pool.state == PoolState::Approved || pool.state == PoolState::Ongoing, + Error::::InvalidPoolState + ); + + let r#type = pool.r#type; + let trading_pair = pool.trading_pair; + + let pool_retired = PoolInfo { + state: PoolState::Retired, + block_retired: Some(frame_system::Pallet::::block_number()), + ..pool + }; + TotalPoolInfos::::insert(pid, pool_retired); + + Self::deposit_event(Event::PoolRetired(pid, r#type, trading_pair)); + + Ok(().into()) + } + /// User deposits some token to a liquidity-pool. /// /// The extrinsic will: @@ -585,7 +622,7 @@ pub mod pallet { let user = ensure_signed(origin)?; let mut pool: PoolInfo = - Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire(pid).try_update(); + Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire().try_update(); ensure!( pool.state == PoolState::Approved || pool.state == PoolState::Ongoing, @@ -598,7 +635,7 @@ pub mod pallet { Self::user_deposit_data(&user, &pid).unwrap_or(DepositData::::from_pool(&pool)); if pool.state == PoolState::Ongoing && pool.update_b != deposit_data.update_b { - pool.try_settle_and_transfer(&mut deposit_data, pid, user.clone())?; + pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; } deposit_data.deposit = deposit_data.deposit.saturating_add(value); @@ -660,7 +697,7 @@ pub mod pallet { let user = ensure_signed(origin)?; let mut pool: PoolInfo = - Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire(pid).try_update(); + Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire().try_update(); ensure!( pool.state == PoolState::Ongoing || pool.state == PoolState::Retired, @@ -671,7 +708,7 @@ pub mod pallet { Self::user_deposit_data(&user, &pid).ok_or(Error::::NoDepositOfUser)?; if pool.update_b != deposit_data.update_b { - pool.try_settle_and_transfer(&mut deposit_data, pid, user.clone())?; + pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; } // Keep minimum deposit in pool when the pool is ongoing. @@ -758,7 +795,7 @@ pub mod pallet { let user = ensure_signed(origin)?; let mut pool: PoolInfo = - Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire(pid).try_update(); + Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire().try_update(); ensure!( pool.state == PoolState::Ongoing || pool.state == PoolState::Retired, @@ -769,7 +806,7 @@ pub mod pallet { Self::user_deposit_data(&user, &pid).ok_or(Error::::NoDepositOfUser)?; ensure!(pool.update_b != deposit_data.update_b, Error::::TooShortBetweenTwoClaim); - pool.try_settle_and_transfer(&mut deposit_data, pid, user.clone())?; + pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; TotalPoolInfos::::insert(pid, pool); TotalDepositData::::insert(user, pid, deposit_data); @@ -842,6 +879,7 @@ pub mod pallet { update_b: Zero::zero(), state: PoolState::UnderAudit, block_startup: None, + block_retired: None, }; TotalPoolInfos::::insert(pool_id, mining_pool); @@ -895,7 +933,7 @@ pub mod pallet { // Check whether pool-activated is meet the startup condition for pid in Self::approved_pids() { if let Some(mut pool) = Self::pool(pid) { - pool = pool.try_startup(pid, n); + pool = pool.try_startup(n); if pool.state == PoolState::Ongoing { ApprovedPoolIds::::mutate(|pids| pids.remove(&pid)); From f6de499750a7152cb71e6f9b2305793e5a5a052a Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 15:07:10 +0800 Subject: [PATCH 05/12] :white_check_mark: ($PALLET) Unit-tests for force_retire_pool --- pallets/liquidity-mining/src/tests.rs | 246 ++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index d40d5e309a..25e0f40f26 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -1475,6 +1475,204 @@ fn double_claim_in_same_block_should_fail() { }); } +#[test] +fn force_retire_pool_approved_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); + + assert_ok!(LM::force_retire_pool( + pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), + 0 + )); + + assert_noop!(LM::deposit(Some(RICHER).into(), 0, UNIT), Error::::InvalidPoolState); + + assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::TooShortBetweenTwoClaim); + assert_noop!(LM::claim(Some(USER_2).into(), 0), Error::::TooShortBetweenTwoClaim); + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + assert_ok!(LM::redeem(Some(USER_2).into(), 0)); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).reserved, 0); + + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, REWARD_AMOUNT); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, REWARD_AMOUNT); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(USER_2, 0).is_none()); + }); +} + +#[test] +fn force_retire_pool_ongoing_should_work() { + const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; + + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + + run_to_block(100); + + assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); + + run_to_block(200); + + assert_ok!(LM::force_retire_pool( + pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), + 0 + )); + assert_noop!(LM::deposit(Some(RICHER).into(), 0, UNIT), Error::::InvalidPoolState); + + assert_ok!(LM::claim(Some(USER_1).into(), 0)); + assert_ok!(LM::claim(Some(USER_2).into(), 0)); + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + assert_ok!(LM::redeem(Some(USER_2).into(), 0)); + + let pbpd_1 = FixedU128::from((PER_BLOCK, UNIT)); + let reward_step_1 = (pbpd_1 * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); + + let pbpd_2 = FixedU128::from((PER_BLOCK, 2 * UNIT)); + let reward_step_2 = (pbpd_2 * (100 * UNIT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_step_1 + reward_step_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_step_1 + reward_step_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, reward_step_2); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, reward_step_2); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).reserved, 0); + + let remain = REWARD_AMOUNT - (reward_step_1 + 2 * reward_step_2); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, remain); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, remain); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(USER_2, 0).is_none()); + }); +} + +#[test] +fn force_retire_pool_with_wrong_origin_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_noop!(LM::force_retire_pool(Origin::root(), 0), DispatchError::BadOrigin); + assert_noop!(LM::force_retire_pool(Origin::none(), 0), DispatchError::BadOrigin); + assert_noop!(LM::force_retire_pool(Some(CREATOR).into(), 0), DispatchError::BadOrigin); + }); +} + +#[test] +fn force_retire_pool_with_wrong_pool_state_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + assert_noop!( + LM::force_retire_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0), + Error::::InvalidPoolState + ); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + + run_to_block(DAYS); + + let result = + LM::force_retire_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0); + assert_noop!(result, Error::::InvalidPoolState); + }); +} + #[test] fn simple_integration_test() { new_test_ext().execute_with(|| { @@ -1531,5 +1729,53 @@ fn simple_integration_test() { assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, reward_step_2); assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + + run_to_block(DAYS); + + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + assert_ok!(LM::redeem(Some(USER_2).into(), 0)); + + let reward_step_3 = + (pbpd_2 * ((DAYS - 200) as Balance * UNIT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!( + Tokens::accounts(USER_1, REWARD_1).free, + reward_step_1 + reward_step_2 + reward_step_3 + ); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!( + Tokens::accounts(USER_1, REWARD_2).free, + reward_step_1 + reward_step_2 + reward_step_3 + ); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, reward_step_2 + reward_step_3); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, reward_step_2 + reward_step_3); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).reserved, 0); + + let remain = REWARD_AMOUNT - (reward_step_1 + 2 * reward_step_2 + 2 * reward_step_3); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, remain); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, remain); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(USER_2, 0).is_none()); }); } From 65ebc7a047e6afd7cb8ed910ab1ff5922f4475a6 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 16:04:47 +0800 Subject: [PATCH 06/12] :construction: ($PALLET) Fix bug & Add feature: volunteer_to_redeem --- pallets/liquidity-mining/src/lib.rs | 64 +++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index 76f8247f3d..20edb0d6b7 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -324,6 +324,8 @@ type PoolId = u128; #[frame_support::pallet] pub mod pallet { + use frame_system::RawOrigin; + use super::*; #[pallet::config] @@ -453,9 +455,9 @@ pub mod pallet { pub(crate) type TotalDepositData = StorageDoubleMap< _, Blake2_128Concat, - AccountIdOf, - Blake2_128Concat, PoolId, + Blake2_128Concat, + AccountIdOf, DepositData, >; @@ -592,12 +594,16 @@ pub mod pallet { let r#type = pool.r#type; let trading_pair = pool.trading_pair; - let pool_retired = PoolInfo { - state: PoolState::Retired, - block_retired: Some(frame_system::Pallet::::block_number()), - ..pool - }; - TotalPoolInfos::::insert(pid, pool_retired); + if pool.deposit == Zero::zero() { + TotalPoolInfos::::remove(pid); + } else { + let pool_retired = PoolInfo { + state: PoolState::Retired, + block_retired: Some(frame_system::Pallet::::block_number()), + ..pool + }; + TotalPoolInfos::::insert(pid, pool_retired); + } Self::deposit_event(Event::PoolRetired(pid, r#type, trading_pair)); @@ -631,8 +637,8 @@ pub mod pallet { ensure!(value >= T::MinimumDepositOfUser::get(), Error::::TooLowToDeposit); - let mut deposit_data: DepositData = - Self::user_deposit_data(&user, &pid).unwrap_or(DepositData::::from_pool(&pool)); + let mut deposit_data: DepositData = Self::user_deposit_data(pid, user.clone()) + .unwrap_or(DepositData::::from_pool(&pool)); if pool.state == PoolState::Ongoing && pool.update_b != deposit_data.update_b { pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; @@ -669,7 +675,7 @@ pub mod pallet { let trading_pair = pool.trading_pair; TotalPoolInfos::::insert(pid, pool); - TotalDepositData::::insert(user.clone(), pid, deposit_data); + TotalDepositData::::insert(pid, user.clone(), deposit_data); Self::deposit_event(Event::UserDeposited(pid, r#type, trading_pair, value, user)); @@ -705,7 +711,7 @@ pub mod pallet { ); let mut deposit_data: DepositData = - Self::user_deposit_data(&user, &pid).ok_or(Error::::NoDepositOfUser)?; + Self::user_deposit_data(pid, user.clone()).ok_or(Error::::NoDepositOfUser)?; if pool.update_b != deposit_data.update_b { pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; @@ -773,8 +779,8 @@ pub mod pallet { } match deposit_data.deposit.saturated_into() { - 0u128 => TotalDepositData::::remove(user.clone(), pid), - _ => TotalDepositData::::insert(user.clone(), pid, deposit_data), + 0u128 => TotalDepositData::::remove(pid, user.clone()), + _ => TotalDepositData::::insert(pid, user.clone(), deposit_data), } Self::deposit_event(Event::UserRedeemed(pid, r#type, trading_pair, try_redeemed, user)); @@ -782,6 +788,32 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(1_000)] + pub fn volunteer_to_redeem( + _origin: OriginFor, + pid: PoolId, + account: Option>, + ) -> DispatchResultWithPostInfo { + let pool: PoolInfo = Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire(); + + ensure!(pool.state == PoolState::Retired, Error::::InvalidPoolState); + + let origin = match account { + Some(account) => RawOrigin::Signed(account).into(), + None => { + let (account, _) = TotalDepositData::::iter_prefix(pid) + .next() + .ok_or(Error::::NoDepositOfUser)?; + + RawOrigin::Signed(account).into() + }, + }; + + Self::redeem(origin, pid)?; + + Ok(().into()) + } + /// User claims the rewards from a liquidity-pool. /// /// The extrinsic will: @@ -803,13 +835,13 @@ pub mod pallet { ); let mut deposit_data: DepositData = - Self::user_deposit_data(&user, &pid).ok_or(Error::::NoDepositOfUser)?; + Self::user_deposit_data(pid, user.clone()).ok_or(Error::::NoDepositOfUser)?; ensure!(pool.update_b != deposit_data.update_b, Error::::TooShortBetweenTwoClaim); pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; TotalPoolInfos::::insert(pid, pool); - TotalDepositData::::insert(user, pid, deposit_data); + TotalDepositData::::insert(pid, user, deposit_data); Ok(().into()) } From 6bce5f2158939bc1ff0a336f2c979469aff582d9 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 16:05:17 +0800 Subject: [PATCH 07/12] :white_check_mark: ($PALLET) Fix unit-tests & Add new unit-test --- pallets/liquidity-mining/src/tests.rs | 51 +++++++++++++++------------ 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 25e0f40f26..2ab0a76d4d 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -520,7 +520,7 @@ fn deposit_to_mining_pool_approved_should_work() { let pool = LM::pool(0).unwrap(); assert_eq!(pool.deposit, 2 * deposit); - let deposit_data = LM::user_deposit_data(USER_1, 0).unwrap(); + let deposit_data = LM::user_deposit_data(0, USER_1).unwrap(); assert_eq!(deposit_data.deposit, 2 * deposit); }); } @@ -557,7 +557,7 @@ fn deposit_to_farming_pool_approved_should_work() { let pool = LM::pool(0).unwrap(); assert_eq!(pool.deposit, 2 * deposit); - let deposit_data = LM::user_deposit_data(USER_1, 0).unwrap(); + let deposit_data = LM::user_deposit_data(0, USER_1).unwrap(); assert_eq!(deposit_data.deposit, 2 * deposit); }); } @@ -622,7 +622,7 @@ fn deposit_to_pool_ongoing_should_work() { let pool = LM::pool(0).unwrap(); assert_eq!(pool.deposit, 2 * DEPOSIT_AMOUNT); - let deposit_data = LM::user_deposit_data(USER_2, 0).unwrap(); + let deposit_data = LM::user_deposit_data(0, USER_2).unwrap(); assert_eq!(deposit_data.deposit, DEPOSIT_AMOUNT); }); } @@ -671,9 +671,9 @@ fn deposit_to_pool_ongoing_with_init_deposit_should_work() { assert_eq!(reward.claimed, reward_to_user_1); } - let deposit_data_1 = LM::user_deposit_data(USER_1, 0).unwrap(); + let deposit_data_1 = LM::user_deposit_data(0, USER_1).unwrap(); assert_eq!(deposit_data_1.deposit, 2_000_000); - let deposit_data_2 = LM::user_deposit_data(USER_2, 0).unwrap(); + let deposit_data_2 = LM::user_deposit_data(0, USER_2).unwrap(); assert_eq!(deposit_data_2.deposit, 1_000_000); }); } @@ -725,9 +725,9 @@ fn double_deposit_to_pool_ongoing_in_diff_block_should_work() { assert_eq!(reward.claimed, reward_to_user_2); } - let deposit_data_1 = LM::user_deposit_data(USER_1, 0).unwrap(); + let deposit_data_1 = LM::user_deposit_data(0, USER_1).unwrap(); assert_eq!(deposit_data_1.deposit, 1_000_000); - let deposit_data_2 = LM::user_deposit_data(USER_2, 0).unwrap(); + let deposit_data_2 = LM::user_deposit_data(0, USER_2).unwrap(); assert_eq!(deposit_data_2.deposit, 2_000_000); }); } @@ -771,9 +771,9 @@ fn double_deposit_to_pool_ongoing_in_same_block_should_work() { assert_eq!(reward.claimed, 0); } - let deposit_data_1 = LM::user_deposit_data(USER_1, 0).unwrap(); + let deposit_data_1 = LM::user_deposit_data(0, USER_1).unwrap(); assert_eq!(deposit_data_1.deposit, 1_000_000); - let deposit_data_2 = LM::user_deposit_data(USER_2, 0).unwrap(); + let deposit_data_2 = LM::user_deposit_data(0, USER_2).unwrap(); assert_eq!(deposit_data_2.deposit, 2_000_000); }); } @@ -946,7 +946,7 @@ fn redeem_from_pool_ongoing_should_work() { assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, rewarded); assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); @@ -959,7 +959,7 @@ fn redeem_from_pool_ongoing_should_work() { assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, rewarded); assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); - assert_eq!(LM::user_deposit_data(USER_2, 0).unwrap().deposit, MinimumDeposit::get()); + assert_eq!(LM::user_deposit_data(0, USER_2).unwrap().deposit, MinimumDeposit::get()); assert_eq!(LM::pool(0).unwrap().deposit, MinimumDeposit::get()); }); @@ -1010,7 +1010,7 @@ fn redeem_from_pool_retired_should_work() { assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, rewarded); assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); @@ -1023,7 +1023,7 @@ fn redeem_from_pool_retired_should_work() { assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, rewarded); assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); - assert!(LM::user_deposit_data(USER_2, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); assert!(LM::pool(0).is_none()); @@ -1080,7 +1080,7 @@ fn double_redeem_from_pool_in_diff_state_should_work() { assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, old_rewarded); assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); @@ -1093,7 +1093,7 @@ fn double_redeem_from_pool_in_diff_state_should_work() { assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, old_rewarded); assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); - assert_eq!(LM::user_deposit_data(USER_2, 0).unwrap().deposit, MinimumDeposit::get()); + assert_eq!(LM::user_deposit_data(0, USER_2).unwrap().deposit, MinimumDeposit::get()); assert_eq!(LM::pool(0).unwrap().deposit, MinimumDeposit::get()); @@ -1115,7 +1115,7 @@ fn double_redeem_from_pool_in_diff_state_should_work() { assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, old_rewarded + new_rewarded); assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); - assert!(LM::user_deposit_data(USER_2, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); assert!(LM::pool(0).is_none()); @@ -1536,11 +1536,16 @@ fn force_retire_pool_approved_should_work() { assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); assert!(LM::pool(0).is_none()); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); - assert!(LM::user_deposit_data(USER_2, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); }); } +#[test] +fn force_retire_pool_approved_with_no_deposit_should_work() { + // TODO +} + #[test] fn force_retire_pool_ongoing_should_work() { const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; @@ -1573,6 +1578,8 @@ fn force_retire_pool_ongoing_should_work() { )); assert_noop!(LM::deposit(Some(RICHER).into(), 0, UNIT), Error::::InvalidPoolState); + run_to_block(DAYS - 100); + assert_ok!(LM::claim(Some(USER_1).into(), 0)); assert_ok!(LM::claim(Some(USER_2).into(), 0)); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); @@ -1615,8 +1622,8 @@ fn force_retire_pool_ongoing_should_work() { assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); assert!(LM::pool(0).is_none()); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); - assert!(LM::user_deposit_data(USER_2, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); }); } @@ -1775,7 +1782,7 @@ fn simple_integration_test() { assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, 0); assert!(LM::pool(0).is_none()); - assert!(LM::user_deposit_data(USER_1, 0).is_none()); - assert!(LM::user_deposit_data(USER_2, 0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); }); } From d84a1618efe695c31830b8d4737b320d961d3729 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 16:28:22 +0800 Subject: [PATCH 08/12] :white_check_mark: ($PALLET) Add unit-tests of volunteer_to_redeem --- pallets/liquidity-mining/src/lib.rs | 3 + pallets/liquidity-mining/src/tests.rs | 141 +++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index 20edb0d6b7..048b20d436 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -788,6 +788,9 @@ pub mod pallet { Ok(().into()) } + /// Help someone to redeem the deposit whose deposited in a liquidity-pool. + /// + /// NOTE: The liquidity-pool should be in retired state. #[pallet::weight(1_000)] pub fn volunteer_to_redeem( _origin: OriginFor, diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 2ab0a76d4d..2f628dbc33 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -1243,6 +1243,125 @@ fn redeem_all_deposit_from_pool_ongoing_should_fail() { }); } +#[test] +fn volunteer_to_redeem_should_work() { + const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; + + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); + + run_to_block(DAYS); + + assert_ok!(LM::volunteer_to_redeem(Some(RICHER).into(), 0, Some(USER_1))); + assert_ok!(LM::volunteer_to_redeem(Origin::root(), 0, None)); + + let pbpd = FixedU128::from((PER_BLOCK, 2 * UNIT)); + let reward_to_user = + (pbpd * (DAYS as Balance * UNIT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_to_user); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, REWARD_1).free, reward_to_user); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).free, reward_to_user); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, REWARD_2).reserved, 0); + + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, MINING_DEPOSIT).reserved, 0); + + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).free, UNIT); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).frozen, 0); + assert_eq!(Tokens::accounts(USER_2, MINING_DEPOSIT).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); + }); +} + +#[test] +fn volunteer_to_redeem_with_wrong_pid_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + + run_to_block(DAYS); + + assert_noop!(LM::volunteer_to_redeem(Origin::none(), 1, None), Error::::InvalidPoolId); + }); +} + +#[test] +fn volunteer_to_redeem_with_wrong_state_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + assert_noop!( + LM::volunteer_to_redeem(Origin::none(), 0, None), + Error::::InvalidPoolState + ); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_noop!( + LM::volunteer_to_redeem(Origin::none(), 0, None), + Error::::InvalidPoolState + ); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); + + run_to_block(100); + + assert_noop!( + LM::volunteer_to_redeem(Origin::none(), 0, None), + Error::::InvalidPoolState + ); + }); +} + #[test] fn claim_from_pool_ongoing_should_work() { new_test_ext().execute_with(|| { @@ -1543,7 +1662,27 @@ fn force_retire_pool_approved_should_work() { #[test] fn force_retire_pool_approved_with_no_deposit_should_work() { - // TODO + new_test_ext().execute_with(|| { + assert_ok!(LM::create_mining_pool( + Some(CREATOR).into(), + MINING_TRADING_PAIR, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1 * UNIT, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(LM::force_retire_pool( + pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), + 0 + )); + + assert!(LM::pool(0).is_none()); + }); } #[test] From ade9f2a4aeecd8963f2d5e5ce50569615da38ad1 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Sat, 21 Aug 2021 16:35:27 +0800 Subject: [PATCH 09/12] :white_check_mark: ($PALLET) Richer unit-tests --- pallets/liquidity-mining/src/tests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 2f628dbc33..906bab9c9f 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -1834,6 +1834,14 @@ fn simple_integration_test() { 0 )); + let reserved = PER_BLOCK * DAYS as Balance; + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, REWARD_AMOUNT - reserved); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, reserved); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, REWARD_AMOUNT - reserved); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, reserved); + // It is unable to call Collective::execute(..) which is private; assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); From cc5a00eb36d8badcb73211994195a91a938bb199 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Mon, 23 Aug 2021 11:01:44 +0800 Subject: [PATCH 10/12] :sparkles: ($PALLET) EBF & Forbid claiming from pool-retired --- pallets/liquidity-mining/src/lib.rs | 92 +++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/pallets/liquidity-mining/src/lib.rs b/pallets/liquidity-mining/src/lib.rs index 048b20d436..943895a7cb 100644 --- a/pallets/liquidity-mining/src/lib.rs +++ b/pallets/liquidity-mining/src/lib.rs @@ -206,6 +206,7 @@ impl PoolInfo { pub enum PoolType { Mining, Farming, + EBFarming, } #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)] @@ -373,6 +374,7 @@ pub mod pallet { InvalidPoolId, InvalidPoolState, InvalidPoolOwner, + InvalidPooltype, /// Find duplicate reward when creating the liquidity-pool DuplicateReward, /// When the amount deposited in a liquidity-pool exceeds the `MaximumDepositInPool` @@ -528,6 +530,35 @@ pub mod pallet { Ok(().into()) } + #[pallet::weight(1_000)] + pub fn create_eb_farming_pool( + origin: OriginFor, + index: ParaId, + first_slot: LeasePeriod, + last_slot: LeasePeriod, + main_reward: (CurrencyId, BalanceOf), + option_rewards: Vec<(CurrencyId, BalanceOf)>, + #[pallet::compact] duration: BlockNumberFor, + #[pallet::compact] min_deposit_to_start: BalanceOf, + #[pallet::compact] after_block_to_start: BlockNumberFor, + ) -> DispatchResultWithPostInfo { + #[allow(non_snake_case)] + let trading_pair = Self::vsAssets(index, first_slot, last_slot); + + Self::create_pool( + origin, + trading_pair, + main_reward, + option_rewards, + PoolType::EBFarming, + duration, + min_deposit_to_start, + after_block_to_start, + )?; + + Ok(().into()) + } + #[pallet::weight(1_000)] pub fn approve_pool(origin: OriginFor, pid: PoolId) -> DispatchResultWithPostInfo { let _ = T::ControlOrigin::ensure_origin(origin)?; @@ -651,24 +682,49 @@ pub mod pallet { Error::::ExceedMaximumDeposit ); - // To lock the deposit - if pool.r#type == PoolType::Mining { - let lpt = Self::convert_to_lptoken(pool.trading_pair)?; + // To "lock" the deposit + match pool.r#type { + PoolType::Mining => { + let lpt = Self::convert_to_lptoken(pool.trading_pair)?; - T::MultiCurrency::ensure_can_withdraw(lpt, &user, value) - .map_err(|_e| Error::::NotEnoughToDeposit)?; + T::MultiCurrency::ensure_can_withdraw(lpt, &user, value) + .map_err(|_e| Error::::NotEnoughToDeposit)?; - T::MultiCurrency::extend_lock(DEPOSIT_ID, lpt, &user, deposit_data.deposit)?; - } else { - let (token_a, token_b) = pool.trading_pair; + T::MultiCurrency::extend_lock(DEPOSIT_ID, lpt, &user, deposit_data.deposit)?; + }, + PoolType::Farming => { + let (token_a, token_b) = pool.trading_pair; - T::MultiCurrency::ensure_can_withdraw(token_a, &user, value) - .map_err(|_e| Error::::NotEnoughToDeposit)?; - T::MultiCurrency::ensure_can_withdraw(token_b, &user, value) - .map_err(|_e| Error::::NotEnoughToDeposit)?; + T::MultiCurrency::ensure_can_withdraw(token_a, &user, value) + .map_err(|_e| Error::::NotEnoughToDeposit)?; + T::MultiCurrency::ensure_can_withdraw(token_b, &user, value) + .map_err(|_e| Error::::NotEnoughToDeposit)?; + + T::MultiCurrency::extend_lock( + DEPOSIT_ID, + token_a, + &user, + deposit_data.deposit, + )?; + T::MultiCurrency::extend_lock( + DEPOSIT_ID, + token_b, + &user, + deposit_data.deposit, + )?; + }, + PoolType::EBFarming => { + let (token_a, token_b) = pool.trading_pair; - T::MultiCurrency::extend_lock(DEPOSIT_ID, token_a, &user, deposit_data.deposit)?; - T::MultiCurrency::extend_lock(DEPOSIT_ID, token_b, &user, deposit_data.deposit)?; + ensure!( + T::MultiCurrency::reserved_balance(token_a, &user) >= deposit_data.deposit, + Error::::NotEnoughToDeposit + ); + ensure!( + T::MultiCurrency::reserved_balance(token_b, &user) >= deposit_data.deposit, + Error::::NotEnoughToDeposit + ); + }, } let r#type = pool.r#type; @@ -753,6 +809,7 @@ pub mod pallet { }, } }, + PoolType::EBFarming => {}, }; deposit_data.deposit = left_in_user; @@ -824,7 +881,7 @@ pub mod pallet { /// /// The conditions to claim: /// - User should have enough token deposited in the liquidity-pool; - /// - The liquidity-pool should be in special states: `Ongoing`, `Retired`; + /// - The liquidity-pool should be in special states: `Ongoing`; #[pallet::weight(1_000)] pub fn claim(origin: OriginFor, pid: PoolId) -> DispatchResultWithPostInfo { let user = ensure_signed(origin)?; @@ -832,10 +889,7 @@ pub mod pallet { let mut pool: PoolInfo = Self::pool(pid).ok_or(Error::::InvalidPoolId)?.try_retire().try_update(); - ensure!( - pool.state == PoolState::Ongoing || pool.state == PoolState::Retired, - Error::::InvalidPoolState - ); + ensure!(pool.state == PoolState::Ongoing, Error::::InvalidPoolState); let mut deposit_data: DepositData = Self::user_deposit_data(pid, user.clone()).ok_or(Error::::NoDepositOfUser)?; From ddb0034d91bf3ab4e28eba79708c702718e68a92 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Mon, 23 Aug 2021 11:01:59 +0800 Subject: [PATCH 11/12] :white_check_mark: ($PALLET) Fix unit-tests --- pallets/liquidity-mining/src/tests.rs | 45 ++++++--------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 906bab9c9f..707581e882 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -1413,7 +1413,7 @@ fn claim_from_pool_ongoing_should_work() { } #[test] -fn claim_from_pool_retired_should_work() { +fn claim_from_pool_retired_should_fail() { new_test_ext().execute_with(|| { assert_ok!(LM::create_mining_pool( Some(CREATOR).into(), @@ -1429,38 +1429,11 @@ fn claim_from_pool_retired_should_work() { assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); - assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); run_to_block(DAYS); - assert_ok!(LM::claim(Some(USER_1).into(), 0)); - - let per_block = REWARD_AMOUNT / DAYS as Balance; - - let reserved = per_block * DAYS as Balance; - let free = REWARD_AMOUNT - reserved; - - let pbpd = FixedU128::from((per_block, 2 * UNIT)); - let rewarded: Balance = - (pbpd * (DAYS as Balance * UNIT).into()).into_inner() / FixedU128::accuracy(); - - assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, rewarded); - assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); - assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); - assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, rewarded); - assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); - assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); - - assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, free); - assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); - assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, reserved - rewarded); - assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, free); - assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); - assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, reserved - rewarded); - - let pool: PoolInfo = LM::pool(0).unwrap(); - assert_eq!(pool.rewards.get(&REWARD_1).unwrap().claimed, rewarded); - assert_eq!(pool.rewards.get(&REWARD_2).unwrap().claimed, rewarded); + let result = LM::claim(Some(USER_1).into(), 0); + assert_noop!(result, Error::::InvalidPoolState); }); } @@ -1562,7 +1535,7 @@ fn claim_without_deposit_should_fail() { assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); - run_to_block(DAYS); + run_to_block(DAYS - 1); let result = LM::claim(Some(USER_2).into(), 0); assert_noop!(result, Error::::NoDepositOfUser); @@ -1587,7 +1560,7 @@ fn double_claim_in_same_block_should_fail() { assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); - run_to_block(DAYS); + run_to_block(DAYS - 1); assert_ok!(LM::claim(Some(USER_1).into(), 0)); assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::TooShortBetweenTwoClaim); @@ -1620,8 +1593,8 @@ fn force_retire_pool_approved_should_work() { assert_noop!(LM::deposit(Some(RICHER).into(), 0, UNIT), Error::::InvalidPoolState); - assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::TooShortBetweenTwoClaim); - assert_noop!(LM::claim(Some(USER_2).into(), 0), Error::::TooShortBetweenTwoClaim); + assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::InvalidPoolState); + assert_noop!(LM::claim(Some(USER_2).into(), 0), Error::::InvalidPoolState); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); @@ -1719,8 +1692,8 @@ fn force_retire_pool_ongoing_should_work() { run_to_block(DAYS - 100); - assert_ok!(LM::claim(Some(USER_1).into(), 0)); - assert_ok!(LM::claim(Some(USER_2).into(), 0)); + assert_noop!(LM::claim(Some(USER_1).into(), 0), Error::::InvalidPoolState); + assert_noop!(LM::claim(Some(USER_2).into(), 0), Error::::InvalidPoolState); assert_ok!(LM::redeem(Some(USER_1).into(), 0)); assert_ok!(LM::redeem(Some(USER_2).into(), 0)); From 57d487e6919e415cf6992cc89ec902ecd2fd5ae2 Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Mon, 23 Aug 2021 11:52:12 +0800 Subject: [PATCH 12/12] :white_check_mark: ($PALLET) Compl unit-tests for early-bird farming --- pallets/liquidity-mining/src/tests.rs | 218 ++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index 707581e882..96276e0fc7 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -23,6 +23,7 @@ use frame_support::{ traits::Hooks, }; use node_primitives::{Balance, CurrencyId, TokenSymbol}; +use orml_traits::MultiReservableCurrency; use crate::{mock::*, Error, PoolId, PoolInfo, PoolState, PoolType, TotalPoolInfos}; @@ -1792,6 +1793,223 @@ fn force_retire_pool_with_wrong_pool_state_should_fail() { }); } +#[test] +fn create_eb_farming_pool_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_eb_farming_pool( + Some(CREATOR).into(), + 2001, + 13, + 20, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1_000 * UNIT, + 0 + )); + + let pool = LM::pool(0).unwrap(); + + assert_eq!(pool.r#type, PoolType::EBFarming); + + let per_block = REWARD_AMOUNT / DAYS as Balance; + let reserved = per_block * DAYS as Balance; + let free = REWARD_AMOUNT - reserved; + + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).free, free); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_1).reserved, reserved); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).free, free); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(CREATOR, REWARD_2).reserved, reserved); + }); +} + +#[test] +fn deposit_to_eb_farming_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(LM::create_eb_farming_pool( + Some(CREATOR).into(), + 2001, + 13, + 20, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1_000_000, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_noop!( + LM::deposit(Some(USER_1).into(), 0, MinimumDeposit::get()), + Error::::NotEnoughToDeposit + ); + + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT / 2)); + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT / 2)); + assert_ok!(LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT / 2)); + + run_to_block(100); + + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT / 2)); + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT / 2)); + assert_ok!(LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT / 2)); + + run_to_block(200); + + assert_eq!(Tokens::unreserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT / 2), 0); + assert_eq!(Tokens::unreserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT / 2), 0); + let result = LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT / 2); + assert_noop!(result, Error::::NotEnoughToDeposit); + + run_to_block(300); + + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT / 2)); + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT / 2)); + let result = LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT / 2); + assert_noop!(result, Error::::NotEnoughToDeposit); + }); +} + +#[test] +fn redeem_from_eb_farming_should_work() { + const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; + + new_test_ext().execute_with(|| { + assert_ok!(LM::create_eb_farming_pool( + Some(CREATOR).into(), + 2001, + 13, + 20, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1_000_000, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT)); + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT)); + assert_ok!(LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT)); + + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).free, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).reserved, DEPOSIT_AMOUNT); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).free, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).reserved, DEPOSIT_AMOUNT); + + run_to_block(100); + + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + + let pbpd = FixedU128::from((PER_BLOCK, DEPOSIT_AMOUNT)); + let reward_to_user_1 = + (pbpd * (100 * DEPOSIT_AMOUNT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user_1); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_to_user_1); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert_ok!(LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT - MinimumDeposit::get())); + + run_to_block(DAYS); + + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + + let reward_to_user_2 = (pbpd * ((DAYS - 100) as Balance * DEPOSIT_AMOUNT).into()) + .into_inner() / + FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user_1 + reward_to_user_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_to_user_1 + reward_to_user_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + }); +} + +#[test] +fn claim_from_eb_farming_should_work() { + const PER_BLOCK: Balance = REWARD_AMOUNT / DAYS as Balance; + + new_test_ext().execute_with(|| { + assert_ok!(LM::create_eb_farming_pool( + Some(CREATOR).into(), + 2001, + 13, + 20, + (REWARD_1, REWARD_AMOUNT), + vec![(REWARD_2, REWARD_AMOUNT)], + DAYS, + 1_000_000, + 0 + )); + + // It is unable to call Collective::execute(..) which is private; + assert_ok!(LM::approve_pool(pallet_collective::RawOrigin::Member(TC_MEMBER_1).into(), 0)); + + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_1, &USER_1, DEPOSIT_AMOUNT)); + assert_ok!(Tokens::reserve(FARMING_DEPOSIT_2, &USER_1, DEPOSIT_AMOUNT)); + assert_ok!(LM::deposit(Some(USER_1).into(), 0, DEPOSIT_AMOUNT)); + + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).free, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_1).reserved, DEPOSIT_AMOUNT); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).free, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, FARMING_DEPOSIT_2).reserved, DEPOSIT_AMOUNT); + + run_to_block(100); + + assert_ok!(LM::claim(Some(USER_1).into(), 0)); + + let pbpd = FixedU128::from((PER_BLOCK, DEPOSIT_AMOUNT)); + let reward_to_user_1 = + (pbpd * (100 * DEPOSIT_AMOUNT).into()).into_inner() / FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user_1); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_to_user_1); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + run_to_block(DAYS); + + let result = LM::claim(Some(USER_1).into(), 0); + assert_noop!(result, Error::::InvalidPoolState); + assert_ok!(LM::redeem(Some(USER_1).into(), 0)); + + let reward_to_user_2 = (pbpd * ((DAYS - 100) as Balance * DEPOSIT_AMOUNT).into()) + .into_inner() / + FixedU128::accuracy(); + + assert_eq!(Tokens::accounts(USER_1, REWARD_1).free, reward_to_user_1 + reward_to_user_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_1).reserved, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).free, reward_to_user_1 + reward_to_user_2); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).frozen, 0); + assert_eq!(Tokens::accounts(USER_1, REWARD_2).reserved, 0); + + assert!(LM::pool(0).is_none()); + assert!(LM::user_deposit_data(0, USER_1).is_none()); + }); +} + #[test] fn simple_integration_test() { new_test_ext().execute_with(|| {