From 63393b2247f0a5974904b68cecd09677cb70803b Mon Sep 17 00:00:00 2001 From: Allen Pocket Date: Thu, 15 Jul 2021 11:11:20 +0800 Subject: [PATCH] Refactor salp (#179) * :construction: ($PALLET) Refactor salp - contribute & contribution * :construction: ($PALLET) Refactor withdraw * :construction: ($PALLET) Add refund-pool * :construction: ($PALLET) Refactor salp - delete redeem-pool * :construction: ($PALLET) Refactor salp - delete useless code * :construction: ($PALLET) Refactor salp - fix compile error * :white_check_mark: ($PALLET) Add/Update/Remove unit-tests * :white_check_mark: ($PALLET) Add the unit-tests of refund * :bug: ($PALLET) Fix bug on refund * :sparkles: ($PALLET) Refund can be called again after last refund is over * :recycle: ($PALLET) Refactor contribution for auto unlock * :construction: ($PALLET) Refactor salp - refact redeem * :recycle: ($PALLET) Complete the refactor of redeem * :art: ($PALLET) Format salp * :bug: ($PALLET) Fix the potential bug in refund-pool&redeem-pool * :sparkles: ($PALLET) Add unlock function * :art: ($PALLET) Format code * :bug: ($PALLET) Add check for the balance of redeem-pool * :white_check_mark: ($PALLET) Add the unit-tests of unlock&redeem * :bug: ($PALLET) Fix the bug of release from redeem to bancor * :white_check_mark: ($PALLET) Add unit-tests for Hooks * :art: ($PALLET) Format code * :wrench: ($PALLET) Move dependencies * :fire: ($PALLET) Remove unused config * :ambulance: ($PALLET) Quick fix mock error * minor fix Co-authored-by: Edwin Wang --- Cargo.lock | 15 - node/service/Cargo.toml | 3 +- pallets/salp/Cargo.toml | 5 +- pallets/salp/src/lib.rs | 1002 ++++++++++++++------------ pallets/salp/src/mock.rs | 20 +- pallets/salp/src/tests.rs | 1433 ++++++++++++++++++++++--------------- 6 files changed, 1433 insertions(+), 1045 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 014bf2d2e..bd53e85a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4981,7 +4981,6 @@ dependencies = [ "sp-timestamp", "sp-transaction-pool", "substrate-prometheus-endpoint 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "substrate-prometheus-endpoint 0.9.0 (git+https://github.com/paritytech/substrate?branch=master)", "zenlink-protocol-runtime-api", ] @@ -10253,20 +10252,6 @@ dependencies = [ "tokio 0.2.25", ] -[[package]] -name = "substrate-prometheus-endpoint" -version = "0.9.0" -source = "git+https://github.com/paritytech/substrate?branch=master#deac6324a16fc4128b94a7b4c3826eebcb86917f" -dependencies = [ - "async-std", - "derive_more", - "futures-util", - "hyper 0.13.10", - "log", - "prometheus", - "tokio 0.2.25", -] - [[package]] name = "substrate-prometheus-endpoint" version = "0.9.0" diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index caddd2f2c..174d6038d 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -33,14 +33,13 @@ sp-session = { version = "3.0.0" } sp-storage = { version = "3.0.0" } sp-timestamp = { version = "3.0.0" } sp-transaction-pool = { version = "3.0.0" } -substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" } # Substrate Pallets pallet-transaction-payment-rpc-runtime-api = { version = "3.0.0" } # Substrate Other frame-system-rpc-runtime-api = { version = "3.0.0" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.9.0" } +substrate-prometheus-endpoint = { version = "0.9.0" } # Cumulus dependencies cumulus-client-consensus-aura = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.8" } diff --git a/pallets/salp/Cargo.toml b/pallets/salp/Cargo.toml index 3c15c5a1e..346ebf99e 100644 --- a/pallets/salp/Cargo.toml +++ b/pallets/salp/Cargo.toml @@ -16,12 +16,12 @@ sp-runtime = { version = "3.0.0", default-features = false } sp-arithmetic = { version = "3.0.0", default-features = false } xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.8", default-features = false } xcm-support = { path = "../../xcm-support", default-features = false } -xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.8",default-features = false } -pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.8",default-features = false } orml-traits = { version = "0.4.1-dev", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.8" } [dev-dependencies] +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.8" } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.8" } sp-io = "3.0.0" sp-core = "3.0.0" orml-tokens = "0.4.1-dev" @@ -40,7 +40,6 @@ std = [ "sp-arithmetic/std", "orml-traits/std", "xcm/std", - "xcm-builder/std", "xcm-support/std", "polkadot-parachain/std", ] diff --git a/pallets/salp/src/lib.rs b/pallets/salp/src/lib.rs index 71e7e609f..be43be318 100644 --- a/pallets/salp/src/lib.rs +++ b/pallets/salp/src/lib.rs @@ -59,10 +59,8 @@ impl WeightInfo for TestWeightInfo { pub type AccountIdOf = ::AccountId; #[allow(type_alias_bounds)] -pub type CurrencyOf = ::MultiCurrency; - -#[allow(type_alias_bounds)] -pub type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; +type BalanceOf = + <::MultiCurrency as MultiCurrency>>::Balance; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub enum FundStatus { @@ -70,7 +68,8 @@ pub enum FundStatus { Retired, Success, Failed, - Withdrew, + RefundWithdrew, + RedeemWithdrew, End, } @@ -80,20 +79,6 @@ impl Default for FundStatus { } } -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, Copy)] -pub enum ContributionStatus { - Contributing, - Contributed, - Redeeming, - Redeemed, -} - -impl Default for ContributionStatus { - fn default() -> Self { - ContributionStatus::Contributed - } -} - /// Information on a funding effort for a pre-existing parachain. We assume that the parachain /// ID is known as it's used for the key of the storage item for which this is the value /// (`Funds`). @@ -160,21 +145,83 @@ pub enum WithdrawCall { Withdraw(Withdraw), } +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, Copy)] +pub enum ContributionStatus { + Idle, + Refunded, + Unlocked, + Refunding, + Contributing(BalanceOf), +} + +impl ContributionStatus +where + BalanceOf: frame_support::sp_runtime::traits::Zero + Clone + Copy, +{ + pub fn is_contributing(&self) -> bool { + match self { + Self::Contributing(_) => true, + _ => false, + } + } + + pub fn contributing(&self) -> BalanceOf { + match self { + Self::Contributing(contributing) => *contributing, + _ => frame_support::sp_runtime::traits::Zero::zero(), + } + } +} + +impl Default for ContributionStatus { + fn default() -> Self { + Self::Idle + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, Copy)] +pub enum RedeemStatus { + Idle, + Redeeming(BalanceOf), +} + +impl RedeemStatus +where + BalanceOf: frame_support::sp_runtime::traits::Zero + Clone + Copy, +{ + pub fn is_redeeming(&self) -> bool { + match self { + Self::Redeeming(..) => true, + _ => false, + } + } + + pub fn redeeming(&self) -> BalanceOf { + match self { + Self::Redeeming(redeeming) => *redeeming, + _ => frame_support::sp_runtime::traits::Zero::zero(), + } + } +} + +impl Default for RedeemStatus { + fn default() -> Self { + Self::Idle + } +} + #[frame_support::pallet] pub mod pallet { // Import various types used to declare pallet in scope. use frame_support::{ pallet_prelude::{storage::child, *}, - sp_runtime::traits::{AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, Zero}, + sp_runtime::traits::{AccountIdConversion, CheckedAdd, Hash, Saturating, Zero}, storage::ChildTriePrefixIterator, PalletId, }; use frame_system::pallet_prelude::*; - use node_primitives::{traits::BancorHandler, CurrencyId, LeasePeriod, ParaId}; - use orml_traits::{ - currency::TransferAll, LockIdentifier, MultiCurrency, MultiCurrencyExtended, - MultiLockableCurrency, MultiReservableCurrency, - }; + use node_primitives::{BancorHandler, CurrencyId, LeasePeriod, ParaId}; + use orml_traits::{currency::TransferAll, MultiCurrency, MultiReservableCurrency}; use polkadot_parachain::primitives::Id as PolkadotParaId; use sp_arithmetic::Percent; use sp_std::{convert::TryInto, prelude::*}; @@ -229,10 +276,11 @@ pub mod pallet { #[pallet::constant] type RemoveKeysLimit: Get; + #[pallet::constant] + type SlotLength: Get; + type MultiCurrency: TransferAll> + MultiCurrency, CurrencyId = CurrencyId> - + MultiCurrencyExtended, CurrencyId = CurrencyId> - + MultiLockableCurrency, CurrencyId = CurrencyId> + MultiReservableCurrency, CurrencyId = CurrencyId>; type BancorPool: BancorHandler>; @@ -244,9 +292,6 @@ pub mod pallet { type BifrostXcmExecutor: BifrostXcmExecutor; - #[pallet::constant] - type SlotLength: Get; - /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -272,14 +317,22 @@ pub mod pallet { Withdrew(AccountIdOf, ParaId, BalanceOf), /// Fail on withdraw full balance of a contributor. [who, fund_index, amount] WithdrawFailed(AccountIdOf, ParaId, BalanceOf), - /// Redeeming token(rely-chain) by vsToken/vsBond. [who, fund_index, amount] - Redeeming(AccountIdOf, BalanceOf), - /// Redeemed token(rely-chain) by vsToken/vsBond. [who, fund_index, amount] - Redeemed(AccountIdOf, BalanceOf), - /// Fail on redeem token(rely-chain) by vsToken/vsBond. [who, fund_index, amount] - RedeemFailed(AccountIdOf, BalanceOf), + /// Refunding to account. [who, fund_index, amount] + Refunding(AccountIdOf, ParaId, BalanceOf), + /// Refunded to account. [who, fund_index, amount] + Refunded(AccountIdOf, ParaId, BalanceOf), + /// Fail on refund to account. [who,fund_index, amount] + RefundFailed(AccountIdOf, ParaId, BalanceOf), + /// Redeeming to account. [who, fund_index, first_slot, last_slot, value] + Redeeming(AccountIdOf, ParaId, LeasePeriod, LeasePeriod, BalanceOf), + /// Redeemed to account. [who, fund_index, first_slot, last_slot, value] + Redeemed(AccountIdOf, ParaId, LeasePeriod, LeasePeriod, BalanceOf), + /// Fail on redeem to account. [who, fund_index, first_slot, last_slot, value] + RedeemFailed(AccountIdOf, ParaId, LeasePeriod, LeasePeriod, BalanceOf), /// Fund is dissolved. [fund_index] Dissolved(ParaId), + /// The vsToken/vsBond was be unlocked. [who, fund_index, value] + Unlocked(AccountIdOf, ParaId, BalanceOf), } #[pallet::error] @@ -290,55 +343,44 @@ pub mod pallet { LastSlotBeforeFirstSlot, /// The last slot cannot be more then 3 slots after the first slot. LastSlotTooFarInFuture, - /// The campaign ends before the current block number. The end must be in the future. - CannotEndInPast, /// There was an overflow. Overflow, /// The contribution was below the minimum, `MinContribution`. ContributionTooSmall, + /// The account doesn't have any contribution to the fund. + ZeroContribution, /// Invalid fund index. InvalidParaId, + /// Invalid fund status. + InvalidFundStatus, + /// Invalid contribution status. + InvalidContributionStatus, /// Contributions exceed maximum amount. CapExceeded, - /// The contribution period has already ended. - ContributionPeriodOver, /// The origin of this call is invalid. - InvalidOrigin, - /// This crowdloan does not correspond to a parachain. - NotParachain, - /// This parachain lease is still active and retirement cannot yet begin. - LeaseActive, - /// This parachain's bid or lease is still active and withdraw cannot yet begin. - BidOrLeaseActive, - /// Funds have not yet been returned. - FundsNotReturned, - /// Fund has not yet retired. - FundNotRetired, - /// Fund has not withdrew. - FundNotWithdrew, - /// The crowdloan has not yet ended. - FundNotEnded, + UnauthorizedAccount, /// The fund has been registered. - FundExisted, - /// Fund has been expired. - VSBondExpired, - /// There are no contributions stored in this crowdloan. - NoContributions, - /// This crowdloan has an active parachain and cannot be dissolved. - HasActiveParachain, - /// The crowdloan is not ready to dissolve. Potentially still has a slot or in retirement - /// period. - NotReadyToDissolve, - /// Invalid signature. - InvalidSignature, - /// Invalid fund status. - InvalidFundStatus, - /// Insufficient Balance. - InsufficientBalance, + FundAlreadyCreated, /// Crosschain xcm failed XcmFailed, - /// Contribute Invalid - ContributionInvalid, + /// Don't have enough vsToken/vsBond to refund + NotEnoughReservedAssetsToRefund, + /// Don't have enough token to refund by users + NotEnoughBalanceInRefundPool, + /// Don't have enough vsToken/vsBond to unlock + NotEnoughBalanceToUnlock, + /// The vsBond is expired now + VSBondExpired, + /// The vsBond cannot be redeemed by now + UnRedeemableNow, + /// Dont have enough vsToken/vsBond to redeem + NotEnoughFreeAssetsToRedeem, + /// Dont have enough vsToken/vsBond to unlock when redeem failed + NotEnoughReservedAssetsToUnlockWhenRedeemFailed, + /// Don't have enough token to redeem by users + NotEnoughBalanceInRedeemPool, + /// Invalid redeem status + InvalidRedeemStatus, } /// Tracker for the next available trie index @@ -357,11 +399,28 @@ pub mod pallet { ValueQuery, >; - /// The balance of the token(rely-chain) can be redeemed. + /// The balance can be refunded to users. + #[pallet::storage] + #[pallet::getter(fn refund_pool)] + pub(super) type RefundPool = StorageValue<_, BalanceOf, ValueQuery>; + + /// The balance can be redeemed to users. #[pallet::storage] #[pallet::getter(fn redeem_pool)] pub(super) type RedeemPool = StorageValue<_, BalanceOf, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn redeem_status)] + pub(super) type RedeemExtras = StorageDoubleMap< + _, + Blake2_128Concat, + AccountIdOf, + Blake2_128Concat, + (ParaId, LeasePeriod, LeasePeriod), + RedeemStatus>, + ValueQuery, + >; + #[pallet::call] impl Pallet { #[pallet::weight(0)] @@ -369,28 +428,31 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] index: ParaId, ) -> DispatchResult { - Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin)?; let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; ensure!(fund.status == FundStatus::Ongoing, Error::::InvalidFundStatus); - Funds::::mutate(index, |fund| { - if let Some(fund) = fund { - fund.status = FundStatus::Success; - } - }); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let fund_new = FundInfo { status: FundStatus::Success, ..fund }; + Funds::::insert(index, Some(fund_new)); Ok(()) } #[pallet::weight(0)] pub fn fund_fail(origin: OriginFor, #[pallet::compact] index: ParaId) -> DispatchResult { - Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin)?; // crownload is failed, so enable the withdrawal function of vsToken/vsBond - let mut fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; ensure!(fund.status == FundStatus::Ongoing, Error::::InvalidFundStatus); - fund.status = FundStatus::Failed; - Funds::::insert(index, Some(fund.clone())); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let fund_new = FundInfo { status: FundStatus::Failed, ..fund }; + Funds::::insert(index, Some(fund_new)); Ok(()) } @@ -400,31 +462,78 @@ pub mod pallet { origin: OriginFor, #[pallet::compact] index: ParaId, ) -> DispatchResult { - Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin)?; let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; ensure!(fund.status == FundStatus::Success, Error::::InvalidFundStatus); - Funds::::mutate(index, |fund| { - if let Some(fund) = fund { - fund.status = FundStatus::Retired; - } - }); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let fund_new = FundInfo { status: FundStatus::Retired, ..fund }; + Funds::::insert(index, Some(fund_new)); Ok(()) } #[pallet::weight(0)] pub fn fund_end(origin: OriginFor, #[pallet::compact] index: ParaId) -> DispatchResult { - Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin)?; - let mut fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - ensure!(fund.status == FundStatus::Withdrew, Error::::InvalidFundStatus); - fund.status = FundStatus::End; - Funds::::insert(index, Some(fund.clone())); + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + ensure!( + fund.status == FundStatus::RefundWithdrew || + fund.status == FundStatus::RedeemWithdrew, + Error::::InvalidFundStatus + ); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let fund_new = FundInfo { status: FundStatus::End, ..fund }; + Funds::::insert(index, Some(fund_new)); Ok(()) } + /// Unlock the reserved vsToken/vsBond after fund success + #[pallet::weight(0)] + pub fn unlock( + _origin: OriginFor, + who: AccountIdOf, + #[pallet::compact] index: ParaId, + ) -> DispatchResult { + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + ensure!( + fund.status == FundStatus::Success || + fund.status == FundStatus::Retired || + fund.status == FundStatus::RedeemWithdrew || + fund.status == FundStatus::End, + Error::::InvalidFundStatus + ); + + let (contributed, status) = Self::contribution(fund.trie_index, &who); + ensure!(status == ContributionStatus::Idle, Error::::InvalidContributionStatus); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot); + + let balance = T::MultiCurrency::unreserve(vsToken, &who, contributed); + ensure!(balance == Zero::zero(), Error::::NotEnoughBalanceToUnlock); + let balance = T::MultiCurrency::unreserve(vsBond, &who, contributed); + ensure!(balance == Zero::zero(), Error::::NotEnoughBalanceToUnlock); + + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Unlocked, + ); + + Self::deposit_event(Event::::Unlocked(who, index, contributed)); + + Ok(()) + } + + /// TODO: Refactor the docs. /// Create a new crowdloaning campaign for a parachain slot deposit for the current auction. #[pallet::weight(T::WeightInfo::create())] pub fn create( @@ -436,8 +545,7 @@ pub mod pallet { ) -> DispatchResult { let depositor = ensure_signed(origin)?; - // There should not be an existing fund. - ensure!(!Funds::::contains_key(index), Error::::FundExisted); + ensure!(!Funds::::contains_key(index), Error::::FundAlreadyCreated); ensure!(first_slot <= last_slot, Error::::LastSlotBeforeFirstSlot); @@ -448,7 +556,7 @@ pub mod pallet { let deposit = T::SubmissionDeposit::get(); - T::MultiCurrency::reserve(Self::token(), &depositor, deposit)?; + T::MultiCurrency::reserve(T::DepositToken::get(), &depositor, deposit)?; Funds::::insert( index, @@ -469,6 +577,7 @@ pub mod pallet { Ok(()) } + /// TODO: Refactor the docs. /// Contribute to a crowd sale. This will transfer some balance over to fund a parachain /// slot. It will be withdrawable in two instances: the parachain becomes retired; or the /// slot is unable to be purchased and the timeout expires. @@ -480,25 +589,25 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin.clone())?; - ensure!(value >= T::MinContribution::get(), Error::::ContributionTooSmall); - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; ensure!(fund.status == FundStatus::Ongoing, Error::::InvalidFundStatus); + + ensure!(value >= T::MinContribution::get(), Error::::ContributionTooSmall); + let raised = fund.raised.checked_add(&value).ok_or(Error::::Overflow)?; ensure!(raised <= fund.cap, Error::::CapExceeded); - let (_, status) = Self::contribution_get(fund.trie_index, &who); - - ensure!(status == ContributionStatus::Contributed, Error::::ContributionInvalid); + let (contributed, status) = Self::contribution(fund.trie_index, &who); + ensure!(status == ContributionStatus::Idle, Error::::InvalidContributionStatus); Self::xcm_ump_contribute(origin, index, value).map_err(|_e| Error::::XcmFailed)?; - let _balance = Self::update_contribution( - index, - who.clone(), - Zero::zero(), - ContributionStatus::Contributing, - )?; + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Contributing(value), + ); Self::deposit_event(Event::Contributing(who, index, value)); @@ -510,16 +619,61 @@ pub mod pallet { pub fn confirm_contribute( origin: OriginFor, who: AccountIdOf, - index: ParaId, - #[pallet::compact] value: BalanceOf, + #[pallet::compact] index: ParaId, is_success: bool, ) -> DispatchResult { - Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin)?; + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - ensure!(fund.status == FundStatus::Ongoing, Error::::InvalidFundStatus); - let (_, status) = Self::contribution_get(fund.trie_index, &who); - ensure!(status == ContributionStatus::Contributing, Error::::ContributionInvalid); - Self::contribute_callback(who, index, value, is_success) + let can_confirm = fund.status == FundStatus::Ongoing || + fund.status == FundStatus::Failed || + fund.status == FundStatus::Success; + ensure!(can_confirm, Error::::InvalidFundStatus); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let (contributed, status) = Self::contribution(fund.trie_index, &who); + ensure!(status.is_contributing(), Error::::InvalidContributionStatus); + let contributing = status.contributing(); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot); + + if is_success { + // Issue reserved vsToken/vsBond to contributor + T::MultiCurrency::deposit(vsToken, &who, contributing)?; + T::MultiCurrency::reserve(vsToken, &who, contributing)?; + T::MultiCurrency::deposit(vsBond, &who, contributing)?; + T::MultiCurrency::reserve(vsBond, &who, contributing)?; + + // Update the raised of fund + let fund_new = + FundInfo { raised: fund.raised.saturating_add(contributing), ..fund }; + Funds::::insert(index, Some(fund_new)); + + // Update the contribution of who + let contributed_new = contributed.saturating_add(contributing); + Self::put_contribution( + fund.trie_index, + &who, + contributed_new, + ContributionStatus::Idle, + ); + + Self::deposit_event(Event::Contributed(who, index, contributing)); + } else { + // Update the contribution of who + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Idle, + ); + + Self::deposit_event(Event::ContributeFailed(who, index, contributing)); + } + + Ok(()) } /// Withdraw full balance of the parachain. this function may need to be called multiple @@ -527,19 +681,17 @@ pub mod pallet { /// - `index`: The parachain to whose crowdloan the contribution was made. #[pallet::weight(0)] pub fn withdraw(origin: OriginFor, #[pallet::compact] index: ParaId) -> DispatchResult { - let owner = Self::check_fund_owner(origin.clone(), index)?; + let depositor = ensure_signed(origin.clone())?; - let fund: FundInfo, BalanceOf, LeasePeriod> = - Self::funds(index).ok_or(Error::::InvalidParaId)?; + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + let can = fund.status == FundStatus::Failed || fund.status == FundStatus::Retired; + ensure!(can, Error::::InvalidFundStatus); - ensure!( - fund.status == FundStatus::Failed || fund.status == FundStatus::Retired, - Error::::InvalidFundStatus - ); + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); - Self::xcm_ump_withdraw(origin, index).map_err(|_e| Error::::XcmFailed)?; + Self::xcm_ump_withdraw(origin, index).map_err(|_| Error::::XcmFailed)?; - Self::deposit_event(Event::Withdrawing(owner, index, fund.raised)); + Self::deposit_event(Event::Withdrawing(depositor, index, fund.raised)); Ok(()) } @@ -548,106 +700,237 @@ pub mod pallet { #[pallet::weight(0)] pub fn confirm_withdraw( origin: OriginFor, - index: ParaId, + #[pallet::compact] index: ParaId, is_success: bool, ) -> DispatchResult { - let owner = Self::check_fund_owner(origin, index)?; + let depositor = ensure_signed(origin)?; + + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + let can = fund.status == FundStatus::Failed || fund.status == FundStatus::Retired; + ensure!(can, Error::::InvalidFundStatus); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + + let amount_withdrew = fund.raised; + + if is_success { + if fund.status == FundStatus::Retired { + RedeemPool::::set(Self::redeem_pool().saturating_add(amount_withdrew)); + + let fund_new = FundInfo { status: FundStatus::RedeemWithdrew, ..fund }; + Funds::::insert(index, Some(fund_new)); + } else if fund.status == FundStatus::Failed { + RefundPool::::set(Self::refund_pool().saturating_add(amount_withdrew)); + + let fund_new = FundInfo { status: FundStatus::RefundWithdrew, ..fund }; + Funds::::insert(index, Some(fund_new)); + } + + Self::deposit_event(Event::Withdrew(depositor, index, amount_withdrew)); + } else { + Self::deposit_event(Event::WithdrawFailed(depositor, index, amount_withdrew)); + } + + Ok(()) + } + + #[pallet::weight(0)] + pub fn refund(origin: OriginFor, #[pallet::compact] index: ParaId) -> DispatchResult { + let who = ensure_signed(origin.clone())?; + + let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + ensure!(fund.status == FundStatus::RefundWithdrew, Error::::InvalidFundStatus); + + let (contributed, status) = Self::contribution(fund.trie_index, &who); + ensure!(contributed > Zero::zero(), Error::::ZeroContribution); + ensure!(status == ContributionStatus::Idle, Error::::InvalidContributionStatus); - let fund: FundInfo, BalanceOf, LeasePeriod> = - Self::funds(index).ok_or(Error::::InvalidParaId)?; + ensure!(Self::refund_pool() >= contributed, Error::::NotEnoughBalanceInRefundPool); + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot); ensure!( - fund.status == FundStatus::Failed || fund.status == FundStatus::Retired, - Error::::InvalidFundStatus + T::MultiCurrency::reserved_balance(vsToken, &who) >= contributed, + Error::::NotEnoughReservedAssetsToRefund + ); + ensure!( + T::MultiCurrency::reserved_balance(vsBond, &who) >= contributed, + Error::::NotEnoughReservedAssetsToRefund ); - Self::withdraw_callback(owner, index, is_success) + + Self::xcm_ump_transfer(origin, index, contributed) + .map_err(|_| Error::::XcmFailed)?; + + RefundPool::::set(Self::refund_pool().saturating_sub(contributed)); + + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Refunding, + ); + + Self::deposit_event(Event::Refunding(who, index, contributed)); + + Ok(()) } #[pallet::weight(0)] - pub fn redeem( + pub fn confirm_refund( origin: OriginFor, + who: AccountIdOf, #[pallet::compact] index: ParaId, - value: BalanceOf, + is_success: bool, ) -> DispatchResult { - let who = ensure_signed(origin.clone())?; + let depositor = ensure_signed(origin)?; let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + ensure!(fund.status == FundStatus::RefundWithdrew, Error::::InvalidFundStatus); - // TODO: Look like the following code be needless - ensure!(fund.status == FundStatus::Withdrew, Error::::FundNotWithdrew); + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); - // TODO: Temp solution, move the check to `Assets` later. - let cur_block = >::block_number(); - let end_block = (fund.last_slot + 1) * T::LeasePeriod::get(); - ensure!( - cur_block < end_block || (cur_block - end_block) <= T::VSBondValidPeriod::get(), - Error::::VSBondExpired - ); + let (contributed, status) = Self::contribution(fund.trie_index, &who); + ensure!(status == ContributionStatus::Refunding, Error::::InvalidContributionStatus); - let new_redeem_balance = - Self::redeem_pool().checked_sub(&value).ok_or(Error::::InsufficientBalance)?; + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, fund.first_slot, fund.last_slot); - let (_, status) = Self::contribution_get(fund.trie_index, &who); + if is_success { + // Slash the reserved vsToken/vsBond + let balance = T::MultiCurrency::slash_reserved(vsToken, &who, contributed); + ensure!(balance == Zero::zero(), Error::::NotEnoughReservedAssetsToRefund); + let balance = T::MultiCurrency::slash_reserved(vsBond, &who, contributed); + ensure!(balance == Zero::zero(), Error::::NotEnoughReservedAssetsToRefund); + + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Refunded, + ); - // TODO: The people who do `redeem` dont have to be contributors - // Remove it - ensure!( - status == ContributionStatus::Contributed || status == ContributionStatus::Redeemed, - Error::::ContributionInvalid - ); + Self::deposit_event(Event::Refunded(who, index, contributed)); + } else { + RefundPool::::set(Self::refund_pool().saturating_add(contributed)); - let vstoken = Self::vstoken(); - let vsbond = Self::vsbond(index, fund.first_slot, fund.last_slot); - Self::check_balance(index, &who, value)?; + Self::put_contribution( + fund.trie_index, + &who, + contributed, + ContributionStatus::Idle, + ); - // TODO: Fix the bug - // It's no way to know the amount of vsToken/vsBond under a specific lock - // So the bug cannot be fixed by now - // Lock the vsToken/vsBond. - T::MultiCurrency::extend_lock(REDEEM_LOCK, vstoken, &who, value)?; - T::MultiCurrency::extend_lock(REDEEM_LOCK, vsbond, &who, value)?; + Self::deposit_event(Event::RefundFailed(who, index, contributed)); + } - Self::xcm_ump_redeem(origin, index, value).map_err(|_e| Error::::XcmFailed)?; + Ok(()) + } - RedeemPool::::put(new_redeem_balance); + #[pallet::weight(0)] + pub fn redeem( + origin: OriginFor, + #[pallet::compact] index: ParaId, + #[pallet::compact] first_slot: LeasePeriod, + #[pallet::compact] last_slot: LeasePeriod, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin.clone())?; - let _balance = Self::update_contribution( - index, + ensure!(Self::redeem_pool() >= value, Error::::NotEnoughBalanceInRedeemPool); + + let cur_block = >::block_number(); + ensure!(!Self::is_expired(cur_block, last_slot), Error::::VSBondExpired); + ensure!(Self::can_redeem(cur_block, last_slot), Error::::UnRedeemableNow); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, first_slot, last_slot); + + T::MultiCurrency::reserve(vsToken, &who, value) + .map_err(|_| Error::::NotEnoughFreeAssetsToRedeem)?; + T::MultiCurrency::reserve(vsBond, &who, value) + .map_err(|_| Error::::NotEnoughFreeAssetsToRedeem)?; + + let status = Self::redeem_status(who.clone(), (index, first_slot, last_slot)); + ensure!(status == RedeemStatus::Idle, Error::::InvalidRedeemStatus); + + Self::xcm_ump_transfer(origin.clone(), index, value) + .map_err(|_| Error::::XcmFailed)?; + + RedeemPool::::set(Self::redeem_pool().saturating_sub(value)); + + RedeemExtras::::insert( who.clone(), - Zero::zero(), - ContributionStatus::Redeeming, - )?; + (index, first_slot, last_slot), + RedeemStatus::Redeeming(value), + ); - Self::deposit_event(Event::Redeeming(who, value)); + Self::deposit_event(Event::Redeeming(who, index, first_slot, last_slot, value)); Ok(()) } - /// Confirm redeem by fund owner temporarily #[pallet::weight(0)] pub fn confirm_redeem( origin: OriginFor, who: AccountIdOf, - index: ParaId, - value: BalanceOf, + #[pallet::compact] index: ParaId, + #[pallet::compact] first_slot: LeasePeriod, + #[pallet::compact] last_slot: LeasePeriod, is_success: bool, ) -> DispatchResult { - Self::check_fund_owner(origin, index)?; - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - ensure!(fund.status == FundStatus::Withdrew, Error::::FundNotWithdrew); - let (_, status) = Self::contribution_get(fund.trie_index, &who); - ensure!(status == ContributionStatus::Redeeming, Error::::ContributionInvalid); - Self::redeem_callback(who, index, value, is_success) + use RedeemStatus as RS; + + ensure_root(origin).map_err(|_| Error::::UnauthorizedAccount)?; + + let status = Self::redeem_status(who.clone(), (index, first_slot, last_slot)); + ensure!(status.is_redeeming(), Error::::InvalidRedeemStatus); + let value = status.redeeming(); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Self::vsAssets(index, first_slot, last_slot); + + if is_success { + let balance = T::MultiCurrency::slash_reserved(vsToken, &who, value); + ensure!(balance == Zero::zero(), Error::::NotEnoughFreeAssetsToRedeem); + let balance = T::MultiCurrency::slash_reserved(vsBond, &who, value); + ensure!(balance == Zero::zero(), Error::::NotEnoughFreeAssetsToRedeem); + + RedeemExtras::::insert(who.clone(), (index, first_slot, last_slot), RS::Idle); + + Self::deposit_event(Event::Redeemed(who, index, first_slot, last_slot, value)); + } else { + let balance = T::MultiCurrency::unreserve(vsToken, &who, value); + ensure!( + balance == Zero::zero(), + Error::::NotEnoughReservedAssetsToUnlockWhenRedeemFailed + ); + let balance = T::MultiCurrency::unreserve(vsBond, &who, value); + ensure!( + balance == Zero::zero(), + Error::::NotEnoughReservedAssetsToUnlockWhenRedeemFailed + ); + + RedeemPool::::set(Self::redeem_pool().saturating_add(value)); + + RedeemExtras::::insert(who.clone(), (index, first_slot, last_slot), RS::Idle); + + Self::deposit_event(Event::RedeemFailed(who, index, first_slot, last_slot, value)); + } + + Ok(()) } /// Remove a fund after the retirement period has ended and all funds have been returned. #[pallet::weight(0)] pub fn dissolve(origin: OriginFor, #[pallet::compact] index: ParaId) -> DispatchResult { - let owner = Self::check_fund_owner(origin, index)?; + let depositor = ensure_signed(origin)?; let mut fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - ensure!(fund.status == FundStatus::End, Error::::FundNotEnded); + ensure!(fund.status == FundStatus::End, Error::::InvalidFundStatus); + + ensure!(depositor == fund.depositor, Error::::UnauthorizedAccount); + // TODO: Delete element when iter? Fix it? let mut refund_count = 0u32; // Try killing the crowdloan child trie and Assume everyone will be refunded. let contributions = Self::contribution_iterator(fund.trie_index); @@ -658,16 +941,17 @@ pub mod pallet { all_refunded = false; break; } - Self::contribution_kill(fund.trie_index, &who); + Self::kill_contribution(fund.trie_index, &who); fund.raised = fund.raised.saturating_sub(balance); refund_count += 1; } if all_refunded == true { - T::MultiCurrency::unreserve(Self::token(), &owner, fund.deposit); + T::MultiCurrency::unreserve(T::DepositToken::get(), &depositor, fund.deposit); Funds::::remove(index); Self::deposit_event(Event::::Dissolved(index)); } + Ok(()) } } @@ -675,34 +959,29 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(n: BlockNumberFor) { - // Release x% KSM/DOT from 1:1 redeem-pool to bancor-pool per cycle. - if (n % T::ReleaseCycle::get()) == 0 { - if let Ok(redeem_pool_balance) = TryInto::::try_into(Self::redeem_pool()) { - // Calculate the release amount by `(redeem_pool_balance * - // T::ReleaseRatio).main_part()`. - let release_amount = T::ReleaseRatio::get() * redeem_pool_balance; - - // Must be ok. + // Release x% KSM/DOT from redeem-pool to bancor-pool per cycle + if n != 0 && (n % T::ReleaseCycle::get()) == 0 { + if let Ok(rp_balance) = TryInto::::try_into(Self::redeem_pool()) { + // Calculate the release amount + let release_amount = T::ReleaseRatio::get() * rp_balance; + + // Must be ok if let Ok(release_amount) = TryInto::>::try_into(release_amount) { - // Decrease the balance of redeem-pool by release amount. - RedeemPool::::mutate(|b| { - *b = b.saturating_sub(release_amount); - }); - - // Increase the balance of bancor-pool by release amount. - if let Err(err) = T::BancorPool::add_token( - T::RelyChainToken::get().into(), - release_amount, - ) { + RedeemPool::::set(Self::redeem_pool().saturating_sub(release_amount)); + + // Increase the balance of bancor-pool by release-amount + if let Err(err) = + T::BancorPool::add_token(T::RelyChainToken::get(), release_amount) + { log::warn!("Bancor: {:?} on bifrost-bancor.", err); } + } else { + log::warn!("Overflow: The balance of redeem-pool exceeds u128."); } - } else { - log::warn!("Overflow: The balance of redeem-pool exceeds u128."); } } - // TODO: check & lock if vsBond if expired ??? + // TODO: Auto unlock vsToken/vsBond? } fn on_initialize(_n: BlockNumberFor) -> frame_support::weights::Weight { @@ -712,93 +991,107 @@ pub mod pallet { } impl Pallet { - /// The account ID of the fund pot. - /// - /// This actually does computation. If you need to keep using it, then make sure you cache - /// the value and only call this once. - pub fn fund_account_id(index: ParaId) -> AccountIdOf { - T::PalletId::get().into_sub_account(index) - } - - pub fn id_from_index(index: TrieIndex) -> child::ChildInfo { + pub(crate) fn id_from_index(index: TrieIndex) -> child::ChildInfo { let mut buf = Vec::new(); buf.extend_from_slice(&(T::PalletId::get().0)); buf.extend_from_slice(&index.encode()[..]); child::ChildInfo::new_default(T::Hashing::hash(&buf[..]).as_ref()) } - pub fn contribution_put( + pub(crate) fn contribution( index: TrieIndex, who: &AccountIdOf, - balance: &BalanceOf, - status: ContributionStatus, - ) { - who.using_encoded(|b| child::put(&Self::id_from_index(index), b, &(balance, status))); - } - - pub fn contribution_get( - index: TrieIndex, - who: &AccountIdOf, - ) -> (BalanceOf, ContributionStatus) { + ) -> (BalanceOf, ContributionStatus>) { who.using_encoded(|b| { - child::get_or_default::<(BalanceOf, ContributionStatus)>( + child::get_or_default::<(BalanceOf, ContributionStatus>)>( &Self::id_from_index(index), b, ) }) } - pub fn contribution_kill(index: TrieIndex, who: &AccountIdOf) { - who.using_encoded(|b| child::kill(&Self::id_from_index(index), b)); - } - - pub fn crowdloan_kill(index: TrieIndex) -> child::KillStorageResult { - child::kill_storage(&Self::id_from_index(index), Some(T::RemoveKeysLimit::get())) - } - - pub fn contribution_iterator( + pub(crate) fn contribution_iterator( index: TrieIndex, - ) -> ChildTriePrefixIterator<(AccountIdOf, (BalanceOf, ContributionStatus))> { + ) -> ChildTriePrefixIterator<( + AccountIdOf, + (BalanceOf, ContributionStatus>), + )> { ChildTriePrefixIterator::<_>::with_prefix_over_key::( &Self::id_from_index(index), &[], ) } - pub fn update_contribution( + pub(crate) fn next_trie_index() -> Result> { + CurrentTrieIndex::::try_mutate(|ti| { + *ti = ti.checked_add(1).ok_or(Error::::Overflow)?; + Ok(*ti - 1) + }) + } + + #[allow(non_snake_case)] + pub(crate) fn vsAssets( index: ParaId, - who: AccountIdOf, - value: BalanceOf, - status: ContributionStatus, - ) -> Result, Error> { - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; + first_slot: LeasePeriod, + last_slot: LeasePeriod, + ) -> (CurrencyId, CurrencyId) { + let token_symbol = *T::RelyChainToken::get(); - let (balance, _) = Self::contribution_get(fund.trie_index, &who); + let vsToken = CurrencyId::VSToken(token_symbol); + let vsBond = CurrencyId::VSBond(token_symbol, index, first_slot, last_slot); - let new_balance = match status { - ContributionStatus::Contributing | ContributionStatus::Contributed => { - balance.checked_add(&value).ok_or(Error::::Overflow)? - } - ContributionStatus::Redeeming | ContributionStatus::Redeemed => { - balance.checked_sub(&value).ok_or(Error::::InsufficientBalance)? - } - }; + (vsToken, vsBond) + } + + /// Check if the vsBond is `past` the redeemable date + pub(crate) fn is_expired(block: BlockNumberFor, last_slot: LeasePeriod) -> bool { + let block_begin_redeem = Self::block_end_of_lease_period_index(last_slot); + let block_end_redeem = block_begin_redeem + T::VSBondValidPeriod::get(); + + block >= block_end_redeem + } + + /// Check if the vsBond is `in` the redeemable date + pub(crate) fn can_redeem(block: BlockNumberFor, last_slot: LeasePeriod) -> bool { + let block_begin_redeem = Self::block_end_of_lease_period_index(last_slot); + let block_end_redeem = block_begin_redeem + T::VSBondValidPeriod::get(); + + block >= block_begin_redeem && block < block_end_redeem + } + + #[allow(unused)] + pub(crate) fn block_start_of_lease_period_index(slot: LeasePeriod) -> BlockNumberFor { + slot * T::LeasePeriod::get() + } - Self::contribution_put(fund.trie_index, &who, &new_balance, status); + pub(crate) fn block_end_of_lease_period_index(slot: LeasePeriod) -> BlockNumberFor { + (slot + 1) * T::LeasePeriod::get() + } + + fn put_contribution( + index: TrieIndex, + who: &AccountIdOf, + contributed: BalanceOf, + status: ContributionStatus>, + ) { + who.using_encoded(|b| { + child::put(&Self::id_from_index(index), b, &(contributed, status)) + }); + } - Ok(new_balance) + fn kill_contribution(index: TrieIndex, who: &AccountIdOf) { + who.using_encoded(|b| child::kill(&Self::id_from_index(index), b)); } - pub fn xcm_ump_contribute( + fn xcm_ump_contribute( origin: OriginFor, - para_id: ParaId, + index: ParaId, value: BalanceOf, ) -> XcmResult { let origin_location: MultiLocation = T::ExecuteXcmOrigin::ensure_origin(origin).map_err(|_e| XcmError::BadOrigin)?; - let contribution = - Contribution { index: para_id, value: value.clone(), signature: None }; + let contribution = Contribution { index, value: value.clone(), signature: None }; let call = CrowdloanContributeCall::CrowdloanContribute(ContributeCall::Contribute( contribution, @@ -810,7 +1103,7 @@ pub mod pallet { let _result = T::BifrostXcmExecutor::ump_transfer_asset( origin_location.clone(), - MultiLocation::X1(Junction::Parachain(para_id)), + MultiLocation::X1(Junction::Parachain(index)), amount, true, )?; @@ -818,216 +1111,31 @@ pub mod pallet { T::BifrostXcmExecutor::ump_transact(origin_location, call) } - pub fn xcm_ump_withdraw(origin: OriginFor, para_id: ParaId) -> XcmResult { + fn xcm_ump_withdraw(origin: OriginFor, index: ParaId) -> XcmResult { let origin_location: MultiLocation = T::ExecuteXcmOrigin::ensure_origin(origin).map_err(|_e| XcmError::BadOrigin)?; - let who: AccountIdOf = PolkadotParaId::from(para_id).into_account(); + let who: AccountIdOf = PolkadotParaId::from(index).into_account(); - let withdraw = Withdraw { who, index: para_id }; + let withdraw = Withdraw { who, index }; let call = CrowdloanWithdrawCall::CrowdloanWithdraw(WithdrawCall::Withdraw(withdraw)) .encode() .into(); T::BifrostXcmExecutor::ump_transact(origin_location, call) } - pub fn xcm_ump_redeem( - origin: OriginFor, - para_id: ParaId, - value: BalanceOf, - ) -> XcmResult { + fn xcm_ump_transfer(origin: OriginFor, index: ParaId, value: BalanceOf) -> XcmResult { let origin_location: MultiLocation = T::ExecuteXcmOrigin::ensure_origin(origin).map_err(|_e| XcmError::BadOrigin)?; let amount = TryInto::::try_into(value).map_err(|_| XcmError::Unimplemented)?; T::BifrostXcmExecutor::ump_transfer_asset( - MultiLocation::X1(Junction::Parachain(para_id)), + MultiLocation::X1(Junction::Parachain(index)), origin_location, amount, false, ) } - - pub(crate) fn next_trie_index() -> Result> { - CurrentTrieIndex::::try_mutate(|ti| { - *ti = ti.checked_add(1).ok_or(Error::::Overflow)?; - Ok(*ti - 1) - }) - } - - fn check_fund_owner( - origin: OriginFor, - para_id: ParaId, - ) -> Result, Error> { - let owner = ensure_signed(origin).map_err(|_e| Error::::InvalidOrigin)?; - let fund = Self::funds(para_id).ok_or(Error::::InvalidParaId)?; - ensure!(owner == fund.depositor, Error::::InvalidOrigin); - Ok(owner) - } - - pub fn check_balance( - para_id: ParaId, - who: &AccountIdOf, - value: BalanceOf, - ) -> Result<(), Error> { - let fund = Self::funds(para_id).ok_or(Error::::InvalidParaId)?; - T::MultiCurrency::ensure_can_withdraw(Self::vstoken(), who, value) - .map_err(|_e| Error::::InsufficientBalance)?; - T::MultiCurrency::ensure_can_withdraw( - Self::vsbond(para_id, fund.first_slot, fund.last_slot), - &who, - value, - ) - .map_err(|_e| Error::::InsufficientBalance)?; - Ok(()) - } - - pub fn token() -> CurrencyId { - T::DepositToken::get() - } - - pub fn vstoken() -> CurrencyId { - CurrencyId::VSToken(*T::RelyChainToken::get()) - } - - pub fn vsbond( - index: ParaId, - first_slot: LeasePeriod, - last_slot: LeasePeriod, - ) -> CurrencyId { - CurrencyId::VSBond(*T::RelyChainToken::get(), index, first_slot, last_slot) - } - - fn contribute_callback( - who: AccountIdOf, - index: ParaId, - value: BalanceOf, - is_success: bool, - ) -> DispatchResult { - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - let vstoken = Self::vstoken(); - let vsbond = Self::vsbond(index, fund.first_slot, fund.last_slot); - - if is_success { - // Issue lock vsToken/vsBond to contributor. - T::MultiCurrency::deposit(vstoken, &who, value)?; - T::MultiCurrency::deposit(vsbond, &who, value)?; - - // Recalculate fund raised. - Funds::::mutate(index, |fund| { - if let Some(fund) = fund { - fund.raised = fund.raised.saturating_add(value); - } - }); - - // Recalculate the contribution of contributor to the fund. - let _ = Self::update_contribution( - index, - who.clone(), - value, - ContributionStatus::Contributed, - )?; - - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - let (balance, _) = Self::contribution_get(fund.trie_index, &who); - T::MultiCurrency::extend_lock(vslock(index), vstoken, &who, balance)?; - T::MultiCurrency::extend_lock(vslock(index), vsbond, &who, balance)?; - - Self::deposit_event(Event::Contributed(who, index, value)); - } else { - // Reset the contribution status - let _ = Self::update_contribution( - index, - who.clone(), - Zero::zero(), - ContributionStatus::Contributed, - ); - - Self::deposit_event(Event::ContributeFailed(who, index, value)); - } - - Ok(()) - } - - fn withdraw_callback( - who: AccountIdOf, - index: ParaId, - is_success: bool, - ) -> DispatchResult { - let mut fund: FundInfo, BalanceOf, LeasePeriod> = - Self::funds(index).ok_or(Error::::InvalidParaId)?; - - if is_success { - RedeemPool::::mutate(|balance| { - *balance = balance.saturating_add(fund.raised); - }); - - fund.status = FundStatus::Withdrew; - Funds::::insert(index, Some(fund.clone())); - - // TODO: Look likes too heavy! - // Require an new `Tokens` module to support special lock. - for (who, _) in Self::contribution_iterator(fund.trie_index) { - T::MultiCurrency::remove_lock(vslock(index), Self::vstoken(), &who)?; - T::MultiCurrency::remove_lock( - vslock(index), - Self::vsbond(index, fund.first_slot, fund.last_slot), - &who, - )?; - } - - Self::deposit_event(Event::Withdrew(who, index, fund.raised)); - } else { - Self::deposit_event(Event::WithdrawFailed(who, index, fund.raised)); - } - - Ok(()) - } - - fn redeem_callback( - who: AccountIdOf, - index: ParaId, - value: BalanceOf, - is_success: bool, - ) -> DispatchResult { - let fund = Self::funds(index).ok_or(Error::::InvalidParaId)?; - - let vstoken = Self::vstoken(); - let vsbond = Self::vsbond(index, fund.first_slot, fund.last_slot); - - T::MultiCurrency::remove_lock(REDEEM_LOCK, vstoken, &who)?; - T::MultiCurrency::remove_lock(REDEEM_LOCK, vsbond, &who)?; - - if is_success { - // Burn the vsToken/vsBond. - T::MultiCurrency::withdraw(vstoken, &who, value)?; - T::MultiCurrency::withdraw(vsbond, &who, value)?; - - // Update contribution trie - let _balance = Self::update_contribution( - index, - who.clone(), - value, - ContributionStatus::Redeemed, - )?; - - Self::deposit_event(Event::Redeemed(who, value)); - } else { - // Revoke the redeem pool. - let new_redeem_balance = Self::redeem_pool().saturating_add(value); - RedeemPool::::put(new_redeem_balance); - - Self::deposit_event(Event::RedeemFailed(who, value)); - } - - Ok(()) - } } - - pub const fn vslock(index: ParaId) -> LockIdentifier { - (index as u64).to_be_bytes() - } - - const REDEEM_LOCK: LockIdentifier = *b"REDEEMLC"; } diff --git a/pallets/salp/src/mock.rs b/pallets/salp/src/mock.rs index 1495d674a..0f4493e88 100644 --- a/pallets/salp/src/mock.rs +++ b/pallets/salp/src/mock.rs @@ -35,12 +35,12 @@ use xcm_support::BifrostXcmExecutor; use crate as salp; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; -type Signature = sp_runtime::MultiSignature; pub(crate) type AccountId = <::Signer as sp_runtime::traits::IdentifyAccount>::AccountId; -type BlockNumber = u32; -type Index = u32; +pub(crate) type Block = frame_system::mocking::MockBlock; +pub(crate) type BlockNumber = u32; +pub(crate) type Index = u32; +pub(crate) type Signature = sp_runtime::MultiSignature; +pub(crate) type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; construct_runtime!( pub enum Test where @@ -119,19 +119,18 @@ impl bifrost_bancor::Config for Test { type WeightInfo = (); } -// TODO: Impl bifrost_xcm_executor::Config - parameter_types! { pub const SubmissionDeposit: u32 = 1; - pub const MinContribution: Balance = 1 * DOLLARS; + pub const MinContribution: Balance = 10; pub const BifrostCrowdloanId: PalletId = PalletId(*b"bf/salp#"); pub const RemoveKeysLimit: u32 = 50; pub const TokenType: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); + pub const SlotLength: BlockNumber = 8u32 as BlockNumber; + + pub const LeasePeriod: BlockNumber = 6 * WEEKS; pub const VSBondValidPeriod: BlockNumber = 30 * DAYS; pub const ReleaseCycle: BlockNumber = 1 * DAYS; - pub const LeasePeriod: BlockNumber = 6 * WEEKS; pub const ReleaseRatio: Percent = Percent::from_percent(50); - pub const SlotLength: BlockNumber = 8u32 as BlockNumber; pub const DepositTokenType: CurrencyId = CurrencyId::Token(TokenSymbol::ASG); } @@ -207,7 +206,6 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub const DOLLARS: Balance = 1_000_000_000_000; // These time units are defined in number of blocks. pub const MINUTES: BlockNumber = 60 / (12 as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; diff --git a/pallets/salp/src/tests.rs b/pallets/salp/src/tests.rs index c8dbc1249..02d721c31 100644 --- a/pallets/salp/src/tests.rs +++ b/pallets/salp/src/tests.rs @@ -18,14 +18,15 @@ // Ensure we're `no_std` when compiling for Wasm. -use frame_support::{assert_noop, assert_ok, dispatch::DispatchError}; +use frame_support::{assert_noop, assert_ok, dispatch::DispatchError, traits::BalanceStatus as BS}; +use orml_traits::{MultiCurrency, MultiReservableCurrency}; use crate::{mock::*, ContributionStatus, Error, FundStatus}; #[test] fn create_fund_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get(),)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::funds(3_000).ok_or(())); assert_eq!(Salp::current_trie_index(), 1); }); @@ -35,12 +36,12 @@ fn create_fund_should_work() { fn create_fund_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { assert_noop!( - Salp::create(Origin::root(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get()), + Salp::create(Origin::root(), 3_000, 1_000, 1, SlotLength::get()), DispatchError::BadOrigin, ); assert_noop!( - Salp::create(Origin::none(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get()), + Salp::create(Origin::none(), 3_000, 1_000, 1, SlotLength::get()), DispatchError::BadOrigin, ); }); @@ -49,11 +50,11 @@ fn create_fund_with_wrong_origin_should_fail() { #[test] fn create_fund_existed_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get(),),); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( - Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get(),), - Error::::FundExisted, + Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get()), + Error::::FundAlreadyCreated, ); }); } @@ -62,7 +63,7 @@ fn create_fund_existed_should_fail() { fn create_fund_exceed_slot_limit_should_fail() { new_test_ext().execute_with(|| { assert_noop!( - Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 0, SlotLength::get()), + Salp::create(Some(ALICE).into(), 3_000, 1_000, 0, SlotLength::get()), Error::::LastSlotTooFarInFuture, ); }); @@ -72,7 +73,7 @@ fn create_fund_exceed_slot_limit_should_fail() { fn create_fund_first_slot_bigger_than_last_slot_should_fail() { new_test_ext().execute_with(|| { assert_noop!( - Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, SlotLength::get(), 0), + Salp::create(Some(ALICE).into(), 3_000, 1_000, SlotLength::get(), 0), Error::::LastSlotBeforeFirstSlot, ); }); @@ -81,7 +82,7 @@ fn create_fund_first_slot_bigger_than_last_slot_should_fail() { #[test] fn set_fund_success_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); // Check status @@ -93,17 +94,20 @@ fn set_fund_success_should_work() { #[test] fn set_fund_success_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_noop!(Salp::fund_success(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_success(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_success(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_noop!(Salp::fund_success(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::fund_success(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!( + Salp::fund_success(Some(BRUCE).into(), 3_000), + Error::::UnauthorizedAccount + ); }) } #[test] fn set_fund_success_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!(Salp::fund_success(Some(ALICE).into(), 4_000), Error::::InvalidParaId); }); } @@ -111,7 +115,7 @@ fn set_fund_success_with_wrong_para_id_should_fail() { #[test] fn set_fund_success_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_noop!( Salp::fund_success(Some(ALICE).into(), 3_000), @@ -123,7 +127,7 @@ fn set_fund_success_with_wrong_fund_status_should_fail() { #[test] fn set_fund_fail_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); // Check status @@ -135,17 +139,20 @@ fn set_fund_fail_should_work() { #[test] fn set_fund_fail_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_noop!(Salp::fund_fail(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_fail(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_fail(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_noop!(Salp::fund_fail(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::fund_fail(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!( + Salp::fund_fail(Some(BRUCE).into(), 3_000), + Error::::UnauthorizedAccount + ); }); } #[test] fn set_fund_fail_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!(Salp::fund_fail(Some(ALICE).into(), 4_000), Error::::InvalidParaId); }); } @@ -153,7 +160,7 @@ fn set_fund_fail_with_wrong_para_id_should_fail() { #[test] fn set_fund_fail_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_noop!(Salp::fund_fail(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus); }); @@ -162,7 +169,7 @@ fn set_fund_fail_with_wrong_fund_status_should_fail() { #[test] fn set_fund_retire_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); @@ -175,18 +182,21 @@ fn set_fund_retire_should_work() { #[test] fn set_fund_retire_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_noop!(Salp::fund_retire(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_retire(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_retire(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_noop!(Salp::fund_retire(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::fund_retire(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!( + Salp::fund_retire(Some(BRUCE).into(), 3_000), + Error::::UnauthorizedAccount + ); }); } #[test] fn set_fund_retire_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_noop!(Salp::fund_retire(Some(ALICE).into(), 4_000), Error::::InvalidParaId); }); @@ -195,7 +205,7 @@ fn set_fund_retire_with_wrong_para_id_should_fail() { #[test] fn set_fund_retire_with_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( Salp::fund_retire(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus @@ -206,7 +216,7 @@ fn set_fund_retire_with_with_wrong_fund_status_should_fail() { #[test] fn set_fund_end_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); @@ -222,22 +232,22 @@ fn set_fund_end_should_work() { #[test] fn set_fund_end_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_noop!(Salp::fund_end(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_end(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::fund_end(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_noop!(Salp::fund_end(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::fund_end(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::fund_end(Some(BRUCE).into(), 3_000), Error::::UnauthorizedAccount); }); } #[test] fn set_fund_end_with_wrong_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); @@ -250,7 +260,7 @@ fn set_fund_end_with_wrong_wrong_para_id_should_fail() { #[test] fn set_fund_end_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_noop!(Salp::fund_end(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus); @@ -258,140 +268,202 @@ fn set_fund_end_with_wrong_fund_status_should_fail() { } #[test] -fn contribute_should_work() { +fn unlock_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - - let fund_origin = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund_origin.trie_index, &BRUCE); - - // Check the init status - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); - let fund_after = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund_after.trie_index, &BRUCE); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 100); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 100); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); + }); +} - // Ensure `Salp::contribute` not change the state(data in storage) - assert_eq!(fund_origin, fund_after); - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributing); +#[test] +fn unlock_with_wrong_fund_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); + assert_noop!( + Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000), + Error::::InvalidFundStatus + ); + }); +} - let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); +#[test] +fn unlock_when_already_unlocked_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); - // Check the contribution - assert_eq!(balance, 10 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); + assert_noop!( + Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000), + Error::::InvalidContributionStatus + ); + }); +} - // Check the fund raised - let raised_delta = fund.raised.saturating_sub(fund_origin.raised); - assert_eq!(raised_delta, balance); +#[test] +fn unlock_without_enough_reserved_vsassets_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - // Check the status of vsToken/vsBond issued - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 10 * DOLLARS); + // ABSOLUTELY NOT HAPPEN AT NORMAL PROCESS! + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + Tokens::slash_reserved(vsToken, &BRUCE, 50); + Tokens::slash_reserved(vsBond, &BRUCE, 50); + + // ``` + // // The following code will produce a supernatural bug. + // // DONT ASK WHY, I DONT KNOW! + // assert_noop!( + // Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000), + // Error::::NotEnoughBalanceToUnlock + // ); + // ``` + let result = Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000); + assert_noop!(result, Error::::NotEnoughBalanceToUnlock); }); } #[test] -fn double_contribute_should_work() { +fn contribute_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); - // Check the contribution let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); - assert_eq!(balance, 10 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(fund.raised, 100); + assert_eq!(contributed, 100); + assert_eq!(status, ContributionStatus::Idle); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 100); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 100); + }); +} - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); +#[test] +fn double_contribute_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); // Check the contribution let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); - assert_eq!(balance, 20 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); - - // Check the status of vsToken/vsBond issued - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 20 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 20 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 20 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 20 * DOLLARS); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(fund.raised, 200); + assert_eq!(contributed, 200); + assert_eq!(status, ContributionStatus::Idle); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 200); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 200); }); } #[test] fn contribute_when_xcm_error_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, false)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, false)); let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); - - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); - assert_eq!(fund.raised, 0 * DOLLARS); - - // Check the status of vsToken/vsBond issued - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 0 * DOLLARS); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(fund.raised, 0); + assert_eq!(contributed, 0); + assert_eq!(status, ContributionStatus::Idle); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); }); } #[test] -fn double_contribute_when_one_of_xcm_error_should_work() { +fn confirm_contribute_later_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, false)); - - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); - - assert_eq!(balance, 10 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); - assert_eq!(fund.raised, balance); - - // Check the status of vsToken/vsBond issued - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 10 * DOLLARS); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(fund.raised, 100); + assert_eq!(contributed, 100); + assert_eq!(status, ContributionStatus::Idle); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 100); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 100); }); } #[test] fn contribute_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_noop!(Salp::contribute(Origin::root(), 3_000, 100), DispatchError::BadOrigin); + assert_noop!(Salp::contribute(Origin::none(), 3_000, 100), DispatchError::BadOrigin); + assert_noop!( - Salp::contribute(Origin::root(), 3_000, 10 * DOLLARS), - DispatchError::BadOrigin + Salp::confirm_contribute(Origin::root(), BRUCE, 3000, true), + DispatchError::BadOrigin, + ); + assert_noop!( + Salp::confirm_contribute(Origin::none(), BRUCE, 3000, true), + DispatchError::BadOrigin, + ); + assert_noop!( + Salp::confirm_contribute(Some(BRUCE).into(), BRUCE, 3000, true), + Error::::UnauthorizedAccount, ); }); } @@ -399,176 +471,175 @@ fn contribute_with_wrong_origin_should_fail() { #[test] fn contribute_with_low_contribution_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( Salp::contribute(Some(BRUCE).into(), 3_000, MinContribution::get() - 1), Error::::ContributionTooSmall ); - }) + }); } #[test] fn contribute_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( - Salp::contribute(Some(BRUCE).into(), 4_000, 10 * DOLLARS), + Salp::contribute(Some(BRUCE).into(), 4_000, 100), Error::::InvalidParaId ); - }) + }); } #[test] fn contribute_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000,)); assert_noop!( - Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS), + Salp::contribute(Some(BRUCE).into(), 3_000, 100), Error::::InvalidFundStatus ); - }) + }); } #[test] fn contribute_exceed_cap_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( - Salp::contribute(Some(BRUCE).into(), 3_000, 1_001 * DOLLARS), + Salp::contribute(Some(BRUCE).into(), 3_000, 1_001), Error::::CapExceeded ); - }) + }); } #[test] -fn contribute_with_wrong_contribution_status_should_fail() { +fn contribute_when_contributing_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_noop!( - Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS), - Error::::ContributionInvalid + Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true), + Error::::InvalidContributionStatus ); }); } #[test] -fn contribute_when_ump_wrong_should_fail() { - // TODO: NEED SERIAL EXEC - // new_test_ext().execute_with(|| { - // assert_ok!(Salp::create( - // Some(ALICE).into(), - // 3_000, - // 1_000 * DOLLARS, - // 1, - // SlotLength::get() - // )); - // - // unsafe { - // MOCK_XCM_RESULT = (false, false); - // } - // - // assert_noop!( - // Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS), - // Error::::XcmFailed, - // ); - // - // unsafe { - // MOCK_XCM_RESULT = (true, true); - // } - // }) +fn confirm_contribute_when_not_in_contributing_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + + assert_noop!( + Salp::contribute(Some(BRUCE).into(), 3_000, 100), + Error::::InvalidContributionStatus + ); + }); +} + +#[test] +fn contribute_with_when_ump_wrong_should_fail() { + // TODO: Require an solution to settle with parallel test workflow } #[test] fn withdraw_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - // Check storage let fund = Salp::funds(3_000).unwrap(); - assert_eq!(fund.status, FundStatus::Withdrew); + assert_eq!(fund.status, FundStatus::RedeemWithdrew); + + assert_eq!(Salp::redeem_pool(), 100); - assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 4_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 4_000, true)); assert_ok!(Salp::fund_fail(Some(ALICE).into(), 4_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, true)); - // Check storage let fund = Salp::funds(4_000).unwrap(); - assert_eq!(fund.status, FundStatus::Withdrew); + assert_eq!(fund.status, FundStatus::RefundWithdrew); + + assert_eq!(Salp::refund_pool(), 100); }); } #[test] fn withdraw_when_xcm_error_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, false)); - // Check storage let fund = Salp::funds(3_000).unwrap(); assert_eq!(fund.status, FundStatus::Retired); - assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_eq!(Salp::redeem_pool(), 0); + + assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 4_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 4_000, true)); assert_ok!(Salp::fund_fail(Some(ALICE).into(), 4_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, false)); - // Check storage let fund = Salp::funds(4_000).unwrap(); assert_eq!(fund.status, FundStatus::Failed); + + assert_eq!(Salp::refund_pool(), 0); }); } #[test] fn double_withdraw_same_fund_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_noop!( - Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true), - Error::::InvalidFundStatus - ); + assert_noop!(Salp::withdraw(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus); - // Check storage let fund = Salp::funds(3_000).unwrap(); - assert_eq!(fund.status, FundStatus::Withdrew); - }); -} + assert_eq!(fund.status, FundStatus::RedeemWithdrew); -#[test] -fn double_withdraw_same_fund_when_xcm_error_should_work() { - new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, false)); - assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, false)); + assert_eq!(Salp::redeem_pool(), 100); - // Check storage - let fund = Salp::funds(3_000).unwrap(); - assert_eq!(fund.status, FundStatus::Retired); + assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 4_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 4_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, true)); + assert_noop!(Salp::withdraw(Some(ALICE).into(), 4_000), Error::::InvalidFundStatus); + + let fund = Salp::funds(4_000).unwrap(); + assert_eq!(fund.status, FundStatus::RefundWithdrew); + + assert_eq!(Salp::refund_pool(), 100); }); } #[test] fn double_withdraw_same_fund_when_one_of_xcm_error_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); @@ -576,29 +647,44 @@ fn double_withdraw_same_fund_when_one_of_xcm_error_should_work() { assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - // Check storage let fund = Salp::funds(3_000).unwrap(); - assert_eq!(fund.status, FundStatus::Withdrew); + assert_eq!(fund.status, FundStatus::RedeemWithdrew); + + assert_eq!(Salp::redeem_pool(), 100); + + assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 4_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 4_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, false)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, true)); + + let fund = Salp::funds(4_000).unwrap(); + assert_eq!(fund.status, FundStatus::RefundWithdrew); + + assert_eq!(Salp::refund_pool(), 100); }); } #[test] fn withdraw_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); - assert_noop!(Salp::withdraw(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::withdraw(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::withdraw(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_noop!(Salp::withdraw(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::withdraw(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::withdraw(Some(BRUCE).into(), 3_000), Error::::UnauthorizedAccount); }); } #[test] fn withdraw_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); @@ -609,7 +695,7 @@ fn withdraw_with_wrong_para_id_should_fail() { #[test] fn withdraw_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_noop!(Salp::withdraw(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus); @@ -622,337 +708,280 @@ fn withdraw_with_when_ump_wrong_should_fail() { } #[test] -fn redeem_should_work() { +fn refund_should_work() { new_test_ext().execute_with(|| { - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + assert_ok!(Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, true)); - assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - - assert_eq!(Salp::redeem_pool(), 0 * DOLLARS); - - // Check the status of vsToken/vsBond issued - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); - - assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 1000 * DOLLARS, true)); - assert_eq!(Salp::redeem_pool(), 0 * DOLLARS); - - // Check the status of vsToken/vsBond issued - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); - }); -} - -// TODO: Reactive after fixing the redeem problem -// #[test] -// fn redeem_by_cathi_should_work() { -// new_test_ext().execute_with(|| { -// let vstoken = Salp::vstoken(); -// let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); -// -// assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); -// assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); -// assert_ok!(Salp::confirm_contribute( -// Some(ALICE).into(), -// BRUCE, -// 3_000, -// 1000 * DOLLARS, -// true -// )); -// assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); -// -// // Transfer vsToken/vsBond to CATHI -// assert_ok!(Tokens::transfer_all(Some(BRUCE).into(), CATHI, vstoken)); -// assert_ok!(Tokens::transfer_all(Some(BRUCE).into(), CATHI, vsbond)); -// -// assert_ok!(Salp::redeem(Some(CATHI).into(), 3_000, 1000 * DOLLARS)); -// -// assert_eq!(Salp::redeem_pool(), 0 * DOLLARS); -// -// // Check the status of vsToken/vsBond issued -// assert_eq!(Tokens::accounts(CATHI, vstoken).free, 1000 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vstoken).frozen, 1000 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vstoken).reserved, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).free, 1000 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).frozen, 1000 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).reserved, 0 * DOLLARS); -// -// assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 1000 * DOLLARS, true)); -// assert_eq!(Salp::redeem_pool(), 0 * DOLLARS); -// -// // Check the status of vsToken/vsBond issued -// assert_eq!(Tokens::accounts(CATHI, vstoken).free, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vstoken).frozen, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vstoken).reserved, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).free, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).frozen, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(CATHI, vsbond).reserved, 0 * DOLLARS); -// }); -// } - -#[test] -fn double_redeem_should_work() { - new_test_ext().execute_with(|| { - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 2_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 500 * DOLLARS, true)); - assert_ok!(Salp::contribute(Some(CATHI).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), CATHI, 3_000, 500 * DOLLARS, true)); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_eq!(Salp::refund_pool(), 0); + + let fund = Salp::funds(3_000).unwrap(); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(contributed, 100); + assert_eq!(status, ContributionStatus::Refunded); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); + }); +} + +#[test] +fn refund_when_xcm_error_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + assert_ok!(Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, false)); - assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 500 * DOLLARS)); - assert_ok!(Salp::redeem(Some(CATHI).into(), 3_000, 500 * DOLLARS)); - - assert_eq!(Salp::redeem_pool(), 0 * DOLLARS); - - // Check the status of vsToken/vsBond issued - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 500 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 500 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 500 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 500 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).free, 500 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).frozen, 500 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).free, 500 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).frozen, 500 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).reserved, 0 * DOLLARS); - - assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 500 * DOLLARS, true)); - assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), CATHI, 3_000, 500 * DOLLARS, true)); - - // Check the status of vsToken/vsBond issued - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).free, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(CATHI, vsbond).reserved, 0 * DOLLARS); - }); -} - -#[test] -fn redeem_with_xcm_error_should_work() { - new_test_ext().execute_with(|| { - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_eq!(Salp::refund_pool(), 100); + + let fund = Salp::funds(3_000).unwrap(); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(contributed, 100); + assert_eq!(status, ContributionStatus::Idle); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 100); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 100); + }); +} + +#[test] +fn double_refund_when_one_of_xcm_error_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + assert_ok!(Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, false)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + assert_ok!(Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, true)); - assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 1000 * DOLLARS, false)); - - assert_eq!(Salp::redeem_pool(), 1000 * DOLLARS); - - // Check the status of vsToken/vsBond issued - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 1000 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 0 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); - }); -} - -// TODO: Reactive after fixing the redeem problem -// #[test] -// fn double_redeem_with_one_of_xcm_error_should_work() { -// new_test_ext().execute_with(|| { -// let vstoken = Salp::vstoken(); -// let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); -// -// assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); -// assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); -// assert_ok!(Salp::confirm_contribute( -// Some(ALICE).into(), -// BRUCE, -// 3_000, -// 1000 * DOLLARS, -// true -// )); -// assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); -// assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); -// -// assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 500 * DOLLARS)); -// assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 500 * DOLLARS)); -// assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 500 * DOLLARS, false)); -// assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 500 * DOLLARS, true)); -// -// assert_eq!(Salp::redeem_pool(), 500 * DOLLARS); -// -// // Check the status of vsToken/vsBond issued -// assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 500 * DOLLARS); -// assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(BRUCE, vstoken).reserved, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 500 * DOLLARS); -// assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 0 * DOLLARS); -// assert_eq!(Tokens::accounts(BRUCE, vsbond).reserved, 0 * DOLLARS); -// }); -// } + assert_eq!(Salp::refund_pool(), 0); + + let fund = Salp::funds(3_000).unwrap(); + let (contributed, status) = Salp::contribution(fund.trie_index, &BRUCE); + assert_eq!(contributed, 100); + assert_eq!(status, ContributionStatus::Refunded); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); + }); +} #[test] -fn redeem_with_wrong_origin_should_fail() { +fn refund_without_enough_vsassets_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_noop!(Salp::redeem(Origin::root(), 3_000, 1000 * DOLLARS), DispatchError::BadOrigin); - assert_noop!(Salp::redeem(Origin::none(), 3_000, 1000 * DOLLARS), DispatchError::BadOrigin); + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_ok!(Tokens::repatriate_reserved(vsToken, &BRUCE, &ALICE, 50, BS::Reserved)); + assert_ok!(Tokens::repatriate_reserved(vsBond, &BRUCE, &ALICE, 50, BS::Reserved)); + + assert_noop!( + Salp::refund(Some(BRUCE).into(), 3_000), + Error::::NotEnoughReservedAssetsToRefund + ); }); } +/// ABSOLUTELY NOT HAPPEN AT NORMAL PROCESS! #[test] -fn redeem_with_wrong_fund_status_should_fail() { +fn refund_without_enough_balance_in_pool_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + // ABSOLUTELY NOT HAPPEN AT NORMAL PROCESS! + crate::pallet::RefundPool::::set(50); assert_noop!( - Salp::redeem(Some(BRUCE).into(), 3_000, 1000 * DOLLARS), - Error::::FundNotWithdrew + Salp::refund(Some(BRUCE).into(), 3_000), + Error::::NotEnoughBalanceInRefundPool ); }); } #[test] -fn redeem_with_expired_vsbond_should_fail() { - // TODO: How to change the current `BlockNumber` of chain? +fn confirm_refund_without_enough_reserved_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + assert_ok!(Tokens::repatriate_reserved(vsToken, &BRUCE, &ALICE, 50, BS::Reserved)); + assert_ok!(Tokens::repatriate_reserved(vsBond, &BRUCE, &ALICE, 50, BS::Reserved)); + + // ``` + // // The following code will produce a supernatural bug. + // // DONT ASK WHY, I DONT KNOW! + // assert_noop!( + // Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, true), + // Error::::NotEnoughReservedAssetsToRefund + // ); + // ``` + let result = Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, true); + assert_noop!(result, Error::::NotEnoughReservedAssetsToRefund); + }); } #[test] -fn redeem_without_enough_balance_in_redeem_pool_should_fail() { - // TODO: Need it? +fn refund_when_refunding_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + + assert_noop!( + Salp::refund(Some(BRUCE).into(), 3_000), + Error::::InvalidContributionStatus + ); + }); } #[test] -fn redeem_without_enough_vstoken_should_fail() { +fn confirm_refund_when_not_in_refunding_should_fail() { new_test_ext().execute_with(|| { - let vstoken = Salp::vstoken(); - - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_ok!(Tokens::transfer_all(Some(BRUCE).into(), ALICE, vstoken)); assert_noop!( - Salp::redeem(Some(BRUCE).into(), 3_000, 1 * DOLLARS), - Error::::InsufficientBalance + Salp::confirm_refund(Some(ALICE).into(), BRUCE, 3_000, true), + Error::::InvalidContributionStatus, ); }); } #[test] -fn redeem_without_enough_vsbond_should_fail() { +fn refund_with_zero_contribution_should_fail() { new_test_ext().execute_with(|| { - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 1000 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - BRUCE, - 3_000, - 1000 * DOLLARS, - true - )); - assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); - assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_noop!(Salp::refund(Some(BRUCE).into(), 3_000), Error::::ZeroContribution); + }); +} + +#[test] +fn refund_with_wrong_origin_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_ok!(Tokens::transfer_all(Some(BRUCE).into(), ALICE, vsbond)); + assert_noop!(Salp::refund(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::refund(Origin::none(), 3_000), DispatchError::BadOrigin); + + assert_ok!(Salp::refund(Some(BRUCE).into(), 3_000)); + assert_noop!( + Salp::confirm_refund(Origin::root(), BRUCE, 3_000, true), + DispatchError::BadOrigin + ); + assert_noop!( + Salp::confirm_refund(Origin::none(), BRUCE, 3_000, true), + DispatchError::BadOrigin + ); assert_noop!( - Salp::redeem(Some(BRUCE).into(), 3_000, 1 * DOLLARS), - Error::::InsufficientBalance + Salp::confirm_refund(Some(BRUCE).into(), BRUCE, 3_000, true), + Error::::UnauthorizedAccount ); }); } #[test] -fn redeem_with_ump_wrong_should_fail() { +fn refund_with_wrong_para_id_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + assert_noop!(Salp::refund(Some(BRUCE).into(), 4_000), Error::::InvalidParaId); + }); +} + +#[test] +fn refund_with_wrong_fund_status_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::fund_fail(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::fund_end(Some(ALICE).into(), 3_000)); + + assert_noop!(Salp::refund(Some(BRUCE).into(), 3_000), Error::::InvalidFundStatus); + + assert_ok!(Salp::create(Some(ALICE).into(), 4_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 4_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 4_000, true)); + + assert_noop!(Salp::refund(Some(BRUCE).into(), 4_000), Error::::InvalidFundStatus); + }); +} + +#[test] +fn refund_with_when_ump_wrong_should_fail() { // TODO: Require an solution to settle with parallel test workflow } @@ -962,17 +991,11 @@ fn dissolve_should_work() { let remove_times = 4; let contribute_account_num = remove_times * RemoveKeysLimit::get(); - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 10_000, 1, SlotLength::get())); for i in 0 .. contribute_account_num { - let racc = AccountId::new([(i as u8); 32]); - assert_ok!(Salp::contribute(Some(racc.clone()).into(), 3_000, 1 * DOLLARS)); - assert_ok!(Salp::confirm_contribute( - Some(ALICE).into(), - racc, - 3_000, - 1 * DOLLARS, - true - )); + let ract = AccountId::new([(i as u8); 32]); + assert_ok!(Salp::contribute(Some(ract.clone()).into(), 3_000, 10)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), ract, 3_000, true)); } assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); @@ -980,42 +1003,37 @@ fn dissolve_should_work() { assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); assert_ok!(Salp::fund_end(Some(ALICE).into(), 3_000)); + assert_eq!(Salp::redeem_pool(), (10 * contribute_account_num) as u128); + for _ in 0 .. remove_times { assert_ok!(Salp::dissolve(Some(ALICE).into(), 3_000)); } - // Check storage assert!(Salp::funds(3_000).is_none()); - - for i in 0 .. contribute_account_num { - let racc = AccountId::new([(i as u8); 32]); - let (balance, status) = Salp::contribution_get(3_000, &racc); - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); - } + assert!(Salp::contribution_iterator(0).next().is_none()); }); } #[test] fn dissolve_with_wrong_origin_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); assert_ok!(Salp::fund_end(Some(ALICE).into(), 3_000)); - assert_noop!(Salp::dissolve(Origin::root(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::dissolve(Origin::none(), 3_000), Error::::InvalidOrigin); - assert_noop!(Salp::dissolve(Some(BRUCE).into(), 3_000), Error::::InvalidOrigin); + assert_noop!(Salp::dissolve(Origin::root(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::dissolve(Origin::none(), 3_000), DispatchError::BadOrigin); + assert_noop!(Salp::dissolve(Some(BRUCE).into(), 3_000), Error::::UnauthorizedAccount); }); } #[test] fn dissolve_with_wrong_para_id_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); @@ -1029,100 +1047,381 @@ fn dissolve_with_wrong_para_id_should_fail() { #[test] fn dissolve_with_wrong_fund_status_should_fail() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_noop!(Salp::dissolve(Some(ALICE).into(), 3_000), Error::::FundNotEnded); + assert_noop!(Salp::dissolve(Some(ALICE).into(), 3_000), Error::::InvalidFundStatus); }); } -// Utilities Test #[test] -fn check_next_trie_index() { +fn redeem_should_work() { new_test_ext().execute_with(|| { - for i in 0 .. 100 { - assert_eq!(Salp::current_trie_index(), i); - assert_ok!(Salp::next_trie_index()); - } + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); + + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + + assert_ok!(>::transfer(vsToken, &BRUCE, &CATHI, 50)); + assert_ok!(>::transfer(vsBond, &BRUCE, &CATHI, 50)); + + assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50)); + assert_ok!(Salp::confirm_redeem(Origin::root(), BRUCE, 3_000, 1, SlotLength::get(), true)); + + assert_eq!(Salp::redeem_pool(), 50); + + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); + + assert_ok!(Salp::redeem(Some(CATHI).into(), 3_000, 1, SlotLength::get(), 50)); + assert_ok!(Salp::confirm_redeem(Origin::root(), CATHI, 3_000, 1, SlotLength::get(), true)); + + assert_eq!(Salp::redeem_pool(), 0); + + assert_eq!(Tokens::accounts(CATHI, vsToken).free, 0); + assert_eq!(Tokens::accounts(CATHI, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(CATHI, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(CATHI, vsBond).free, 0); + assert_eq!(Tokens::accounts(CATHI, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(CATHI, vsBond).reserved, 0); }); } #[test] -fn fund_success_should_work() { +fn redeem_when_xcm_error_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000 * DOLLARS, 1, SlotLength::get())); + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); - let fund_origin = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund_origin.trie_index, &BRUCE); + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); - // Check the init status - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); - let fund_after = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund_after.trie_index, &BRUCE); + assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50)); + assert_ok!(Salp::confirm_redeem(Origin::root(), BRUCE, 3_000, 1, SlotLength::get(), false)); - // Ensure `Salp::contribute` not change the state(data in storage) - assert_eq!(fund_origin, fund_after); - assert_eq!(balance, 0 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributing); + assert_eq!(Salp::redeem_pool(), 100); - assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); + assert_eq!(Tokens::accounts(BRUCE, vsToken).free, 100); + assert_eq!(Tokens::accounts(BRUCE, vsToken).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsToken).reserved, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).free, 100); + assert_eq!(Tokens::accounts(BRUCE, vsBond).frozen, 0); + assert_eq!(Tokens::accounts(BRUCE, vsBond).reserved, 0); + }); +} - // Check the status of vsToken/vsBond issued - let vstoken = Salp::vstoken(); - let vsbond = Salp::vsbond(3_000, 1, SlotLength::get()); - assert_eq!(Tokens::accounts(BRUCE, vstoken).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vstoken).frozen, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).free, 10 * DOLLARS); - assert_eq!(Tokens::accounts(BRUCE, vsbond).frozen, 10 * DOLLARS); +#[test] +fn redeem_when_redeeming_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); - let fund = Salp::funds(3_000).unwrap(); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); - // Check the contribution - assert_eq!(balance, 10 * DOLLARS); - assert_eq!(status, ContributionStatus::Contributed); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50)); - // Check the fund raised - let raised_delta = fund.raised.saturating_sub(fund_origin.raised); - assert_eq!(raised_delta, balance); + let result = Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50); + assert_noop!(result, Error::::InvalidRedeemStatus); + }); +} - // Set fund status +#[test] +fn confirm_redeem_when_not_in_redeeming_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + assert_noop!( + Salp::confirm_redeem(Origin::root(), BRUCE, 3_000, 1, SlotLength::get(), true), + Error::::InvalidRedeemStatus + ); + }); +} - // Withdraw from relaychain +#[test] +fn redeem_with_wrong_origin_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); + + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - // Check fund status - let fund = Salp::funds(3_000).unwrap(); - assert_eq!(fund.status, FundStatus::Withdrew); + assert_noop!( + Salp::redeem(Origin::root(), 3_000, 1, SlotLength::get(), 50), + DispatchError::BadOrigin + ); + assert_noop!( + Salp::redeem(Origin::none(), 3_000, 1, SlotLength::get(), 50), + DispatchError::BadOrigin + ); - // Check redeem pool - assert_eq!(Salp::redeem_pool(), 10 * DOLLARS); + assert_noop!( + Salp::confirm_redeem(Origin::none(), BRUCE, 3_000, 1, SlotLength::get(), true), + Error::::UnauthorizedAccount + ); + assert_noop!( + Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 1, SlotLength::get(), true), + Error::::UnauthorizedAccount + ); + }); +} - // Check token balance - assert_ok!(Salp::check_balance(3_000, &BRUCE, 10 * DOLLARS)); +#[test] +fn redeem_with_expired_vsbond_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); - assert_ok!(Salp::redeem(Some(BRUCE).into(), 3_000, 10 * DOLLARS)); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + let block_end_redeem = block_begin_redeem + VSBondValidPeriod::get(); + System::set_block_number(block_end_redeem); - // Check the contribution - assert_eq!(balance, 10 * DOLLARS); - assert_eq!(status, ContributionStatus::Redeeming); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); - assert_ok!(Salp::confirm_redeem(Some(ALICE).into(), BRUCE, 3_000, 10 * DOLLARS, true)); - let (balance, status) = Salp::contribution_get(fund.trie_index, &BRUCE); + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); - // Check the contribution - assert_eq!(balance, 0); - assert_eq!(status, ContributionStatus::Redeemed); + assert_ok!(>::transfer(vsToken, &BRUCE, &CATHI, 50)); + assert_ok!(>::transfer(vsBond, &BRUCE, &CATHI, 50)); + + assert_noop!( + Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50), + Error::::VSBondExpired + ); + + assert_noop!( + Salp::redeem(Some(CATHI).into(), 3_000, 1, SlotLength::get(), 50), + Error::::VSBondExpired + ); + }); +} + +#[test] +fn redeem_with_not_redeemable_vsbond_should_fail() { + new_test_ext().execute_with(|| { + // Mock redeem-pool already had some balance + crate::pallet::RedeemPool::::set(100); + + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_not_redeemable = LeasePeriod::get(); + System::set_block_number(block_not_redeemable); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + + assert_ok!(>::transfer(vsToken, &BRUCE, &CATHI, 50)); + assert_ok!(>::transfer(vsBond, &BRUCE, &CATHI, 50)); + + assert_noop!( + Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50), + Error::::UnRedeemableNow + ); + + assert_noop!( + Salp::redeem(Some(CATHI).into(), 3_000, 1, SlotLength::get(), 50), + Error::::UnRedeemableNow + ); + }); +} + +#[test] +fn redeem_without_enough_vsassets_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); + + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + #[allow(non_snake_case)] + let (vsToken, vsBond) = Salp::vsAssets(3_000, 1, SlotLength::get()); + + assert_ok!(>::transfer(vsToken, &BRUCE, &CATHI, 50)); + assert_ok!(>::transfer(vsBond, &BRUCE, &CATHI, 50)); + + assert_noop!( + Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 60), + Error::::NotEnoughFreeAssetsToRedeem + ); + + assert_noop!( + Salp::redeem(Some(CATHI).into(), 3_000, 1, SlotLength::get(), 60), + Error::::NotEnoughFreeAssetsToRedeem + ); + }); +} + +#[test] +fn redeem_without_enough_balance_in_pool_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + + // Mock the BlockNumber + let block_begin_redeem = (SlotLength::get() + 1) * LeasePeriod::get(); + System::set_block_number(block_begin_redeem); + + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + + // Before withdraw + assert_noop!( + Salp::redeem(Some(BRUCE).into(), 3_000, 1, SlotLength::get(), 50), + Error::::NotEnoughBalanceInRedeemPool + ); + + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + }); +} + +#[test] +fn redeem_with_when_ump_wrong_should_fail() { + // TODO: Require an solution to settle with parallel test workflow +} + +#[test] +fn release_from_redeem_to_bancor_should_work() { + fn run_to_block(n: BlockNumber) { + use frame_support::traits::Hooks; + while System::block_number() <= n { + Salp::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Salp::on_initialize(System::block_number()); + } + } + + new_test_ext().execute_with(|| { + assert_ok!(Salp::create(Some(ALICE).into(), 3_000, 1_000, 1, SlotLength::get())); + assert_ok!(Salp::contribute(Some(BRUCE).into(), 3_000, 100)); + assert_ok!(Salp::confirm_contribute(Some(ALICE).into(), BRUCE, 3_000, true)); + assert_ok!(Salp::fund_success(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::unlock(Some(BRUCE).into(), BRUCE, 3_000)); + assert_ok!(Salp::fund_retire(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::withdraw(Some(ALICE).into(), 3_000)); + assert_ok!(Salp::confirm_withdraw(Some(ALICE).into(), 3_000, true)); + + run_to_block(ReleaseCycle::get()); + + let release_amount = ReleaseRatio::get() * 100u128; + let redeem_pool_left = 100 - release_amount; + assert_eq!(Salp::redeem_pool(), redeem_pool_left); + + // TODO: Check the balance of bancor(Waiting Bancor to Support..) + }); +} + +// Utilities Test +#[test] +fn check_next_trie_index() { + new_test_ext().execute_with(|| { + for i in 0 .. 100 { + assert_eq!(Salp::current_trie_index(), i); + assert_ok!(Salp::next_trie_index()); + } + }); +} + +#[test] +fn check_is_expired() { + new_test_ext().execute_with(|| { + let slot = 10; + let block_end_of_slot = Salp::block_end_of_lease_period_index(slot); + let block_expired = block_end_of_slot + VSBondValidPeriod::get(); + + assert!(Salp::is_expired(block_expired, slot)); + }); +} + +#[test] +fn check_can_redeem() { + new_test_ext().execute_with(|| { + let slot = 10; + let block_end_of_slot = Salp::block_end_of_lease_period_index(slot); + let block_redeemable = block_end_of_slot + VSBondValidPeriod::get() / 2; + + assert!(Salp::can_redeem(block_redeemable, slot)); }); }