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..943895a7cb 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; @@ -77,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 { @@ -84,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); @@ -100,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, + )); } } @@ -114,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, )); @@ -138,21 +151,21 @@ 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) { 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); @@ -178,7 +191,7 @@ impl PoolInfo { } Pallet::::deposit_event(Event::UserClaimed( - pid, + self.pool_id, self.r#type, self.trading_pair, to_rewards, @@ -193,6 +206,7 @@ impl PoolInfo { pub enum PoolType { Mining, Farming, + EBFarming, } #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug)] @@ -213,13 +227,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 +254,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 +271,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 +297,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; } @@ -311,6 +325,8 @@ type PoolId = u128; #[frame_support::pallet] pub mod pallet { + use frame_system::RawOrigin; + use super::*; #[pallet::config] @@ -358,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` @@ -440,9 +457,9 @@ pub mod pallet { pub(crate) type TotalDepositData = StorageDoubleMap< _, Blake2_128Concat, - AccountIdOf, - Blake2_128Concat, PoolId, + Blake2_128Concat, + AccountIdOf, DepositData, >; @@ -513,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)?; @@ -565,6 +611,36 @@ 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; + + 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)); + + Ok(().into()) + } + /// User deposits some token to a liquidity-pool. /// /// The extrinsic will: @@ -583,7 +659,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, @@ -592,11 +668,11 @@ 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, pid, user.clone())?; + pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; } deposit_data.deposit = deposit_data.deposit.saturating_add(value); @@ -606,31 +682,56 @@ 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; 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)); @@ -658,7 +759,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, @@ -666,10 +767,10 @@ 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, pid, user.clone())?; + pool.try_settle_and_transfer(&mut deposit_data, user.clone())?; } // Keep minimum deposit in pool when the pool is ongoing. @@ -708,6 +809,7 @@ pub mod pallet { }, } }, + PoolType::EBFarming => {}, }; deposit_data.deposit = left_in_user; @@ -734,8 +836,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)); @@ -743,6 +845,35 @@ 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, + 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: @@ -750,27 +881,24 @@ 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)?; 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, - Error::::InvalidPoolState - ); + ensure!(pool.state == PoolState::Ongoing, Error::::InvalidPoolState); 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, pid, user.clone())?; + 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()) } @@ -814,12 +942,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 { @@ -838,6 +968,7 @@ pub mod pallet { update_b: Zero::zero(), state: PoolState::UnderAudit, block_startup: None, + block_retired: None, }; TotalPoolInfos::::insert(pool_id, mining_pool); @@ -891,7 +1022,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)); diff --git a/pallets/liquidity-mining/src/tests.rs b/pallets/liquidity-mining/src/tests.rs index b03ff93f9e..96276e0fc7 100644 --- a/pallets/liquidity-mining/src/tests.rs +++ b/pallets/liquidity-mining/src/tests.rs @@ -16,9 +16,14 @@ // 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 orml_traits::MultiReservableCurrency; use crate::{mock::*, Error, PoolId, PoolInfo, PoolState, PoolType, TotalPoolInfos}; @@ -516,7 +521,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); }); } @@ -553,7 +558,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); }); } @@ -618,7 +623,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); }); } @@ -649,8 +654,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); @@ -666,9 +672,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); }); } @@ -702,8 +708,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); @@ -719,9 +726,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); }); } @@ -765,9 +772,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); }); } @@ -926,8 +933,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)); @@ -940,7 +947,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)); @@ -953,7 +960,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()); }); @@ -989,8 +996,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)); @@ -1003,7 +1011,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)); @@ -1016,7 +1024,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()); @@ -1059,8 +1067,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)); @@ -1073,7 +1081,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)); @@ -1086,15 +1094,16 @@ 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()); // 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)); @@ -1107,7 +1116,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()); @@ -1236,7 +1245,9 @@ fn redeem_all_deposit_from_pool_ongoing_should_fail() { } #[test] -fn claim_from_pool_ongoing_should_work() { +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(), @@ -1254,40 +1265,45 @@ fn claim_from_pool_ongoing_should_work() { assert_ok!(LM::deposit(Some(USER_1).into(), 0, UNIT)); assert_ok!(LM::deposit(Some(USER_2).into(), 0, UNIT)); - run_to_block(100); - - assert_ok!(LM::claim(Some(USER_1).into(), 0)); - - let per_block = REWARD_AMOUNT / DAYS as Balance; + run_to_block(DAYS); - let reserved = per_block * DAYS as Balance; - let free = REWARD_AMOUNT - reserved; + 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 = 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 reward_to_user = + (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).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, rewarded); + 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(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); + 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); - 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); + 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 claim_from_pool_retired_should_work() { +fn volunteer_to_redeem_with_wrong_pid_should_fail() { new_test_ext().execute_with(|| { assert_ok!(LM::create_mining_pool( Some(CREATOR).into(), @@ -1303,19 +1319,79 @@ 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_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(|| { + 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(100); + 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 * DAYS as Balance * 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); @@ -1337,6 +1413,31 @@ fn claim_from_pool_retired_should_work() { }); } +#[test] +fn claim_from_pool_retired_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); + + let result = LM::claim(Some(USER_1).into(), 0); + assert_noop!(result, Error::::InvalidPoolState); + }); +} + #[test] fn claim_with_wrong_pid_should_fail() { new_test_ext().execute_with(|| { @@ -1435,7 +1536,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); @@ -1460,9 +1561,566 @@ 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); }); } + +#[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::::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)); + + 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(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() { + 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] +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); + + run_to_block(DAYS - 100); + + 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)); + + 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(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).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 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(|| { + 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 + )); + + 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)); + + 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); + + 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(0, USER_1).is_none()); + assert!(LM::user_deposit_data(0, USER_2).is_none()); + }); +}