From 083d77f46c0827af95d6d9b83e065ff9a03f2d3f Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 12 Dec 2023 11:37:24 +0100 Subject: [PATCH] Replace mock-accountant macro with mock builder pallet and Rate by Quantity --- Cargo.lock | 1 + libs/mocks/src/pools.rs | 74 ++++++++++++++++- pallets/investments/Cargo.toml | 3 + pallets/investments/src/mock.rs | 132 +++++++++++++++++++++++-------- pallets/investments/src/tests.rs | 14 ++-- 5 files changed, 185 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cec6f8cba7..f895ac38f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7877,6 +7877,7 @@ dependencies = [ name = "pallet-investments" version = "1.0.0" dependencies = [ + "cfg-mocks", "cfg-primitives", "cfg-test-utils", "cfg-traits", diff --git a/libs/mocks/src/pools.rs b/libs/mocks/src/pools.rs index fcaa809501..ba4ce059e6 100644 --- a/libs/mocks/src/pools.rs +++ b/libs/mocks/src/pools.rs @@ -1,6 +1,10 @@ #[frame_support::pallet] pub mod pallet { - use cfg_traits::{PoolInspect, PoolReserve, PriceValue, Seconds, TrancheTokenPrice}; + use cfg_traits::{ + investments::InvestmentAccountant, PoolInspect, PoolReserve, PriceValue, Seconds, + TrancheTokenPrice, + }; + use cfg_types::investments::InvestmentInfo; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::pallet_prelude::*; use mock_builder::{execute_call, register_call}; @@ -64,6 +68,42 @@ pub mod pallet { ) { register_call!(move |(a, b, c)| f(a, b, c)); } + + pub fn mock_info( + f: impl Fn( + T::TrancheCurrency, + ) -> Result< + InvestmentInfo, + DispatchError, + > + 'static, + ) { + register_call!(f); + } + + pub fn mock_balance(f: impl Fn(T::TrancheCurrency, &T::AccountId) -> T::Balance + 'static) { + register_call!(move |(a, b)| f(a, b)); + } + + pub fn mock_transfer( + f: impl Fn(T::TrancheCurrency, &T::AccountId, &T::AccountId, T::Balance) -> DispatchResult + + 'static, + ) { + register_call!(move |(a, b, c, d)| f(a, b, c, d)); + } + + #[allow(non_snake_case)] + pub fn mock_InvestmentAccountant_deposit( + f: impl Fn(&T::AccountId, T::TrancheCurrency, T::Balance) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } + + #[allow(non_snake_case)] + pub fn mock_InvestmentAccountant_withdraw( + f: impl Fn(&T::AccountId, T::TrancheCurrency, T::Balance) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } } impl PoolInspect for Pallet { @@ -88,6 +128,38 @@ pub mod pallet { } } + impl InvestmentAccountant for Pallet { + type Amount = T::Balance; + type Error = DispatchError; + type InvestmentId = T::TrancheCurrency; + type InvestmentInfo = InvestmentInfo; + + fn info(a: Self::InvestmentId) -> Result { + execute_call!(a) + } + + fn balance(a: Self::InvestmentId, b: &T::AccountId) -> Self::Amount { + execute_call!((a, b)) + } + + fn transfer( + a: Self::InvestmentId, + b: &T::AccountId, + c: &T::AccountId, + d: Self::Amount, + ) -> DispatchResult { + execute_call!((a, b, c, d)) + } + + fn deposit(a: &T::AccountId, b: Self::InvestmentId, c: Self::Amount) -> DispatchResult { + execute_call!((a, b, c)) + } + + fn withdraw(a: &T::AccountId, b: Self::InvestmentId, c: Self::Amount) -> DispatchResult { + execute_call!((a, b, c)) + } + } + impl TrancheTokenPrice for Pallet { type BalanceRatio = T::BalanceRatio; type Moment = Seconds; diff --git a/pallets/investments/Cargo.toml b/pallets/investments/Cargo.toml index 5295763165..ded078003d 100644 --- a/pallets/investments/Cargo.toml +++ b/pallets/investments/Cargo.toml @@ -29,6 +29,7 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", default- [dev-dependencies] cfg-test-utils = { path = "../../libs/test-utils" } cfg-types = { path = "../../libs/types" } +cfg-mocks = { workspace = true, default-features = true } orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.43" } orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.43" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } @@ -44,6 +45,7 @@ runtime-benchmarks = [ "cfg-test-utils/runtime-benchmarks", "cfg-traits/runtime-benchmarks", "cfg-types/runtime-benchmarks", + "cfg-mocks/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", @@ -67,6 +69,7 @@ try-runtime = [ "cfg-primitives/try-runtime", "cfg-test-utils/try-runtime", "cfg-traits/try-runtime", + "cfg-mocks/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", "sp-runtime/try-runtime", diff --git a/pallets/investments/src/mock.rs b/pallets/investments/src/mock.rs index e316376f21..b6b034b6bc 100644 --- a/pallets/investments/src/mock.rs +++ b/pallets/investments/src/mock.rs @@ -17,7 +17,7 @@ pub use cfg_primitives::CFG as CURRENCY; use cfg_primitives::*; use cfg_traits::{investments::OrderManager, PreConditions}; use cfg_types::{ - fixed_point::Rate, + fixed_point::Quantity, investments::{InvestmentAccount, InvestmentInfo}, orders::{FulfillmentWithPrice, TotalOrder}, tokens::CurrencyId, @@ -26,7 +26,13 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::DispatchResultWithPostInfo, parameter_types, - traits::{GenesisBuild, Nothing}, + traits::{ + tokens::{ + fungibles::{Inspect, Mutate}, + Fortitude, Precision, Preservation, + }, + GenesisBuild, Nothing, + }, RuntimeDebug, }; use orml_traits::GetByKey; @@ -38,7 +44,12 @@ use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, DispatchResult, }; -use sp_std::convert::{TryFrom, TryInto}; +use sp_std::{ + cell::RefCell, + collections::btree_map::BTreeMap, + convert::{TryFrom, TryInto}, + rc::Rc, +}; pub use crate as pallet_investments; @@ -55,7 +66,8 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Investments: pallet_investments::{Pallet, Call, Storage, Event}, OrmlTokens: orml_tokens::{Pallet, Storage, Event, Config}, - Balances: pallet_balances::{Pallet, Storage, Event} + Balances: pallet_balances::{Pallet, Storage, Event}, + MockAccountant: cfg_mocks::pallet_mock_pools, } ); @@ -136,6 +148,7 @@ impl pallet_balances::Config for MockRuntime { type WeightInfo = (); } +/* cfg_test_utils::mocks::accountant::impl_mock_accountant!( MockAccountant, MockAccountId, @@ -143,6 +156,16 @@ cfg_test_utils::mocks::accountant::impl_mock_accountant!( CurrencyId, Balance ); +*/ + +impl cfg_mocks::pallet_mock_pools::Config for MockRuntime { + type Balance = Balance; + type BalanceRatio = Quantity; + type CurrencyId = CurrencyId; + type PoolId = PoolId; + type TrancheCurrency = InvestmentId; + type TrancheId = TrancheId; +} pub struct NoopCollectHook; impl cfg_traits::StatusNotificationHook for NoopCollectHook { @@ -160,9 +183,10 @@ parameter_types! { } impl pallet_investments::Config for MockRuntime { - type Accountant = MockAccountant; + //type Accountant = MockAccountant; + type Accountant = MockAccountant; type Amount = Balance; - type BalanceRatio = Rate; + type BalanceRatio = Quantity; type CollectedInvestmentHook = NoopCollectHook; type CollectedRedemptionHook = NoopCollectHook; type InvestmentId = InvestmentId; @@ -334,8 +358,14 @@ impl TestExternalitiesBuilder { .assimilate_storage(&mut storage) .unwrap(); - MockAccountant::::init(accountant_mock::Genesis { - infos: vec![ + let mut externalities = TestExternalities::new(storage); + externalities.execute_with(|| { + // We need to set this, otherwise on genesis (i.e. 0) + // no events are stored + System::set_block_number(1); + + // Mocked behaviour for the accountant + let state = Rc::new(RefCell::new(BTreeMap::from([ ( INVESTMENT_0_0, InvestmentInfo { @@ -352,15 +382,55 @@ impl TestExternalitiesBuilder { payment_currency: AUSD_CURRENCY_ID, }, ), - ], + ]))); + + MockAccountant::mock_info({ + let state = state.clone(); + move |id| Ok(state.borrow().get(&id).unwrap().clone()) + }); + + MockAccountant::mock_balance(|id, who| OrmlTokens::balance(id.into(), who)); + + MockAccountant::mock_transfer({ + let state = state.clone(); + move |id, source, dest, amount| { + let _ = state.borrow().get(&id).unwrap(); + >::transfer( + id.into(), + source, + dest, + amount, + Preservation::Expendable, + ) + .map(|_| ()) + } + }); + + MockAccountant::mock_InvestmentAccountant_deposit({ + let state = state.clone(); + move |buyer, id, amount| { + let _ = state.borrow().get(&id).unwrap(); + >::mint_into(id.into(), buyer, amount) + .map(|_| ()) + } + }); + + MockAccountant::mock_InvestmentAccountant_withdraw({ + let state = state.clone(); + move |seller, id, amount| { + let _ = state.borrow().get(&id).unwrap(); + >::burn_from( + id.into(), + seller, + amount, + Precision::Exact, + Fortitude::Polite, + ) + .map(|_| ()) + } + }); }); - let mut externalities = TestExternalities::new(storage); - externalities.execute_with(|| { - // We need to set this, otherwise on genesis (i.e. 0) - // no events are stored - System::set_block_number(1); - }); externalities } } @@ -438,20 +508,20 @@ pub(crate) fn redeem_x_per_investor(amount: Balance) -> DispatchResult { /// # E.g. /// ```rust /// use cfg_primitives::Balance; -/// use cfg_types::Rate; +/// use cfg_types::fixed_point::Quantity; /// /// let rate = crate::mock::price_of(3, 5, 100); /// assert_eq!(rate.into_inner(), 3050000000000000000000000000) // I.e. price is 3,05 /// ``` -pub(crate) fn price_of(full: Balance, dec_n: Balance, dec_d: Balance) -> Rate { - let full = Rate::from_inner(Rate::DIV.saturating_mul(full)); - let decimals = Rate::saturating_from_rational(dec_n, dec_d); +pub(crate) fn price_of(full: Balance, dec_n: Balance, dec_d: Balance) -> Quantity { + let full = Quantity::from_inner(Quantity::DIV.saturating_mul(full)); + let decimals = Quantity::saturating_from_rational(dec_n, dec_d); full.add(decimals) } /// Creates a fulfillment of given perc and price -pub(crate) fn fulfillment_of(perc: Perquintill, price: Rate) -> FulfillmentWithPrice { +pub(crate) fn fulfillment_of(perc: Perquintill, price: Quantity) -> FulfillmentWithPrice { FulfillmentWithPrice { of_amount: perc, price, @@ -460,26 +530,26 @@ pub(crate) fn fulfillment_of(perc: Perquintill, price: Rate) -> FulfillmentWithP /// Fulfills the given fulfillment for INVESTMENT_0_0 on both invest and redeem /// side -pub(crate) fn fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { +pub(crate) fn fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { fulfill_invest_x(fulfillment)?; fulfill_redeem_x(fulfillment) } /// Fulfills the given fulfillment for INVESTMENT_0_0 on the investment side -pub(crate) fn fulfill_invest_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { +pub(crate) fn fulfill_invest_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { let _invest_orders = Investments::process_invest_orders(INVESTMENT_0_0)?; Investments::invest_fulfillment(INVESTMENT_0_0, fulfillment) } /// Fulfills the given fulfillment for INVESTMENT_0_0 on the investment side -pub(crate) fn fulfill_redeem_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { +pub(crate) fn fulfill_redeem_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { let _redeem_orders = Investments::process_redeem_orders(INVESTMENT_0_0)?; Investments::redeem_fulfillment(INVESTMENT_0_0, fulfillment) } /// Invest 50 * CURRENCY per Investor into INVESTMENT_0_0 and fulfills /// the given fulfillment. -pub(crate) fn invest_fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { +pub(crate) fn invest_fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { invest_x_per_investor(50 * CURRENCY)?; let _invest_orders = Investments::process_invest_orders(INVESTMENT_0_0)?; @@ -490,7 +560,7 @@ pub(crate) fn invest_fulfill_x(fulfillment: FulfillmentWithPrice) -> Dispa /// the given fulfillment. pub(crate) fn invest_x_fulfill_x( invest_per_investor: Balance, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, ) -> DispatchResult { invest_x_per_investor(invest_per_investor)?; @@ -502,7 +572,7 @@ pub(crate) fn invest_x_fulfill_x( /// the given fulfillment. pub(crate) fn invest_x_per_fulfill_x( invest_per_investor: Vec<(MockAccountId, Balance)>, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, ) -> DispatchResult { for (who, amount) in invest_per_investor { Investments::update_invest_order(RuntimeOrigin::signed(who), INVESTMENT_0_0, amount)?; @@ -515,7 +585,7 @@ pub(crate) fn invest_x_per_fulfill_x( /// and fulfills the given fulfillment. pub(crate) fn invest_x_runner_fulfill_x( invest_per_investor: Balance, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, runner: F, ) -> DispatchResult where @@ -529,7 +599,7 @@ where /// Redeem 50 * CURRENCY per TrancheHolder into INVESTMENT_0_0 and fulfills /// the given fulfillment. -pub(crate) fn redeem_fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { +pub(crate) fn redeem_fulfill_x(fulfillment: FulfillmentWithPrice) -> DispatchResult { redeem_x_per_investor(50 * CURRENCY)?; let _redeem_orders = Investments::process_redeem_orders(INVESTMENT_0_0); @@ -540,7 +610,7 @@ pub(crate) fn redeem_fulfill_x(fulfillment: FulfillmentWithPrice) -> Dispa /// the given fulfillment. pub(crate) fn redeem_x_fulfill_x( redeem_per_investor: Balance, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, ) -> DispatchResult { redeem_x_per_investor(redeem_per_investor)?; @@ -552,7 +622,7 @@ pub(crate) fn redeem_x_fulfill_x( /// the given fulfillment. pub(crate) fn redeem_x_per_fulfill_x( redeem_per_investor: Vec<(MockAccountId, Balance)>, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, ) -> DispatchResult { for (who, amount) in redeem_per_investor { Investments::update_redeem_order(RuntimeOrigin::signed(who), INVESTMENT_0_0, amount)?; @@ -565,7 +635,7 @@ pub(crate) fn redeem_x_per_fulfill_x( /// closure and fulfills the given fulfillment. pub(crate) fn redeem_x_runner_fulfill_x( redeem_per_investor: Balance, - fulfillment: FulfillmentWithPrice, + fulfillment: FulfillmentWithPrice, runner: F, ) -> DispatchResult where diff --git a/pallets/investments/src/tests.rs b/pallets/investments/src/tests.rs index c9d7d74708..87be5305dc 100644 --- a/pallets/investments/src/tests.rs +++ b/pallets/investments/src/tests.rs @@ -11,7 +11,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_types::fixed_point::Rate; +use cfg_types::fixed_point::Quantity; use frame_support::{assert_noop, assert_ok}; use pallet_investments::Event; use sp_arithmetic::{traits::Saturating, Perquintill}; @@ -622,7 +622,7 @@ fn update_redeem_fails_when_collect_needed() { fn fulfillment_flow_for_everything_works() { TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 2, 10); + let PRICE: Quantity = price_of(1, 2, 10); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT = 50 * CURRENCY; #[allow(non_snake_case)] @@ -821,7 +821,7 @@ fn fulfillment_partially_works_low_price() { // * Collects and orders from users must overflow correctly too TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 288, 334); + let PRICE: Quantity = price_of(1, 288, 334); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT = 50 * CURRENCY; #[allow(non_snake_case)] @@ -1507,7 +1507,7 @@ fn fulfillment_partially_works_high_price() { // * Collects and orders from users must overflow correctly too TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 288, 335); + let PRICE: Quantity = price_of(1, 288, 335); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT = 50 * CURRENCY; #[allow(non_snake_case)] @@ -2183,7 +2183,7 @@ fn fulfillment_partially_works_high_price() { fn fulfillment_of_zero_works() { TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 20, 10); + let PRICE: Quantity = price_of(1, 20, 10); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT = 50 * CURRENCY; #[allow(non_snake_case)] @@ -2506,7 +2506,7 @@ fn fulfillment_of_zero_works() { fn collecting_fully_works() { TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 288, 334); + let PRICE: Quantity = price_of(1, 288, 334); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT_A = 50 * CURRENCY; #[allow(non_snake_case)] @@ -2818,7 +2818,7 @@ fn collecting_fully_works() { fn collecting_over_max_works() { TestExternalitiesBuilder::build().execute_with(|| { #[allow(non_snake_case)] - let PRICE: Rate = price_of(1, 288, 335); + let PRICE: Quantity = price_of(1, 288, 335); #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT = 50 * CURRENCY; #[allow(non_snake_case)]