diff --git a/libs/primitives/src/lib.rs b/libs/primitives/src/lib.rs index 4bc2b25e6d..cf1ad99966 100644 --- a/libs/primitives/src/lib.rs +++ b/libs/primitives/src/lib.rs @@ -283,6 +283,9 @@ pub mod constants { /// The maximum number of pool fees per pool fee bucket pub const MAX_POOL_FEES_PER_BUCKET: u32 = 100; + + /// Identification of the native token of the chain. Used in XCM locations. + pub const NATIVE_KEY: &[u8] = &[0, 1]; } /// Listing of parachains we integrate with. @@ -301,7 +304,7 @@ pub mod parachains { pub mod altair { pub const ID: u32 = 2088; - pub const AIR_KEY: &[u8] = &[0, 1]; + pub const AIR_KEY: &[u8] = crate::NATIVE_KEY; } } @@ -313,7 +316,7 @@ pub mod parachains { pub mod centrifuge { pub const ID: u32 = 2031; - pub const CFG_KEY: &[u8] = &[0, 1]; + pub const CFG_KEY: &[u8] = crate::NATIVE_KEY; } } diff --git a/libs/types/src/tokens.rs b/libs/types/src/tokens.rs index 1173d55835..77c2737b2a 100644 --- a/libs/types/src/tokens.rs +++ b/libs/types/src/tokens.rs @@ -364,6 +364,23 @@ impl CrossChainTransferability { pub fn includes_liquidity_pools(self) -> bool { matches!(self, Self::LiquidityPools) } + + /// Fees will be charged using `FixedRateOfFungible`. + #[cfg(feature = "std")] + pub fn xcm_default() -> Self { + Self::Xcm(XcmMetadata { + fee_per_second: None, + }) + } + + /// Fees will be charged using `AssetRegistryTrader`. + /// If value is 0, no fees will be charged. + #[cfg(feature = "std")] + pub fn xcm_with_fees(value: Balance) -> Self { + Self::Xcm(XcmMetadata { + fee_per_second: Some(value), + }) + } } /// Liquidity Pools-wrapped tokens diff --git a/runtime/altair/src/xcm.rs b/runtime/altair/src/xcm.rs index ab0c2e231b..379a0606e2 100644 --- a/runtime/altair/src/xcm.rs +++ b/runtime/altair/src/xcm.rs @@ -10,10 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::{ - parachains, - types::{EnsureRootOr, HalfOfCouncil}, -}; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_types::tokens::CurrencyId; use frame_support::{ parameter_types, @@ -27,10 +24,9 @@ use pallet_xcm::XcmPassthrough; use runtime_common::{ transfer_filter::PreXcmTransfer, xcm::{ - general_key, AccountIdToLocation, Barrier, FixedConversionRateProvider, + AccountIdToLocation, Barrier, CanonicalNativePerSecond, FixedConversionRateProvider, LocalOriginToLocation, ToTreasury, }, - xcm_fees::native_per_second, }; use sp_core::ConstU32; use staging_xcm::{ @@ -88,25 +84,13 @@ impl staging_xcm_executor::Config for XcmConfig { /// else the xcm executor won't know how to charge fees for a transfer of said /// token. pub type Trader = ( - FixedRateOfFungible>, + FixedRateOfFungible>, AssetRegistryTrader< FixedRateAssetRegistryTrader>, ToTreasury, >, ); -parameter_types! { - // Canonical location: https://github.com/paritytech/polkadot/pull/4470 - pub CanonicalAirPerSecond: (AssetId, u128, u128) = ( - Location::new( - 0, - general_key(parachains::kusama::altair::AIR_KEY) - ).into(), - native_per_second(), - 0, - ); -} - /// Means for transacting the fungibles assets of this parachain. pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation diff --git a/runtime/centrifuge/src/xcm.rs b/runtime/centrifuge/src/xcm.rs index cd1da395a4..ef74d41fe1 100644 --- a/runtime/centrifuge/src/xcm.rs +++ b/runtime/centrifuge/src/xcm.rs @@ -10,10 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::{ - parachains, - types::{EnsureRootOr, HalfOfCouncil}, -}; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_traits::TryConvert; use cfg_types::{tokens::CurrencyId, EVMChainId}; use frame_support::{ @@ -28,10 +25,9 @@ use pallet_xcm::XcmPassthrough; use runtime_common::{ transfer_filter::PreXcmTransfer, xcm::{ - general_key, AccountIdToLocation, Barrier, FixedConversionRateProvider, + AccountIdToLocation, Barrier, CanonicalNativePerSecond, FixedConversionRateProvider, LocalOriginToLocation, LpInstanceRelayer, ToTreasury, }, - xcm_fees::native_per_second, }; use sp_core::ConstU32; use staging_xcm::{ @@ -89,25 +85,13 @@ impl staging_xcm_executor::Config for XcmConfig { /// else the xcm executor won't know how to charge fees for a transfer of said /// token. pub type Trader = ( - FixedRateOfFungible>, + FixedRateOfFungible>, AssetRegistryTrader< FixedRateAssetRegistryTrader>, ToTreasury, >, ); -parameter_types! { - // Canonical location: https://github.com/paritytech/polkadot/pull/4470 - pub CanonicalCfgPerSecond: (AssetId, u128, u128) = ( - Location::new( - 0, - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ).into(), - native_per_second(), - 0, - ); -} - /// Means for transacting the fungibles assets of this parachain. pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation diff --git a/runtime/common/src/xcm.rs b/runtime/common/src/xcm.rs index 56ab50a56d..dbba874d87 100644 --- a/runtime/common/src/xcm.rs +++ b/runtime/common/src/xcm.rs @@ -36,7 +36,7 @@ use staging_xcm_builder::{ TakeWeightCredit, }; -use crate::xcm_fees::default_per_second; +use crate::xcm_fees::{default_per_second, native_per_second}; /// Our FixedConversionRateProvider, used to charge XCM-related fees for /// tokens registered in the asset registry that were not already handled by @@ -71,6 +71,18 @@ pub fn general_key(data: &[u8]) -> staging_xcm::latest::Junction { } } +frame_support::parameter_types! { + // Canonical location: https://github.com/paritytech/polkadot/pull/4470 + pub CanonicalNativePerSecond: (AssetId, u128, u128) = ( + Location::new( + 0, + general_key(cfg_primitives::NATIVE_KEY), + ).into(), + native_per_second(), + 0, + ); +} + /// How we convert an `[AccountId]` into an XCM Location pub struct AccountIdToLocation; impl> Convert for AccountIdToLocation { diff --git a/runtime/development/src/xcm.rs b/runtime/development/src/xcm.rs index b9efe50bed..ac260246f0 100644 --- a/runtime/development/src/xcm.rs +++ b/runtime/development/src/xcm.rs @@ -9,10 +9,7 @@ // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::{ - parachains, - types::{EnsureRootOr, HalfOfCouncil}, -}; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_traits::TryConvert; use cfg_types::{tokens::CurrencyId, EVMChainId}; use frame_support::{ @@ -27,10 +24,9 @@ use pallet_xcm::XcmPassthrough; use runtime_common::{ transfer_filter::PreXcmTransfer, xcm::{ - general_key, AccountIdToLocation, Barrier, FixedConversionRateProvider, + AccountIdToLocation, Barrier, CanonicalNativePerSecond, FixedConversionRateProvider, LocalOriginToLocation, LpInstanceRelayer, ToTreasury, }, - xcm_fees::native_per_second, }; use sp_core::ConstU32; use staging_xcm::{ @@ -95,19 +91,6 @@ pub type Trader = ( >, ); -parameter_types! { - // Canonical location: https://github.com/paritytech/polkadot/pull/4470 - pub CanonicalNativePerSecond: (AssetId, u128, u128) = ( - Location::new( - 0, - general_key(parachains::kusama::altair::AIR_KEY), - ).into(), - native_per_second(), - 0, - ); - -} - /// Means for transacting the fungibles assets of this parachain. pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation diff --git a/runtime/integration-tests/src/generic/cases/assets.rs b/runtime/integration-tests/src/generic/cases/assets.rs new file mode 100644 index 0000000000..56cf7e24bd --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/assets.rs @@ -0,0 +1,54 @@ +use cfg_types::tokens::CurrencyId; +use frame_support::{assert_noop, assert_ok, dispatch::RawOrigin}; +use sp_runtime::{DispatchError, DispatchError::BadOrigin}; + +use crate::{ + generic::{ + config::Runtime, env::Env, envs::runtime_env::RuntimeEnv, utils::currency::default_metadata, + }, + utils::orml_asset_registry, +}; + +#[test_runtimes(all)] +fn authority_configured() { + let mut env = RuntimeEnv::::default(); + + env.parachain_state_mut(|| { + assert_ok!(orml_asset_registry::Pallet::::register_asset( + RawOrigin::Root.into(), + default_metadata(), + Some(CurrencyId::Native) + )); + + assert_ok!(orml_asset_registry::Pallet::::register_asset( + RawOrigin::Root.into(), + default_metadata(), + Some(CurrencyId::ForeignAsset(42)) + )); + + assert_noop!( + orml_asset_registry::Pallet::::register_asset( + RawOrigin::Root.into(), + default_metadata(), + Some(CurrencyId::Tranche(42, [1; 16])) + ), + BadOrigin + ); + }); +} + +#[test_runtimes(all)] +fn processor_configured() { + let mut env = RuntimeEnv::::default(); + + env.parachain_state_mut(|| { + assert_noop!( + orml_asset_registry::Pallet::::register_asset( + RawOrigin::Root.into(), + default_metadata(), + None + ), + DispatchError::Other("asset-registry: AssetId is required") + ); + }); +} diff --git a/runtime/integration-tests/src/generic/cases/currency_conversions.rs b/runtime/integration-tests/src/generic/cases/currency_conversions.rs new file mode 100644 index 0000000000..a690165203 --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/currency_conversions.rs @@ -0,0 +1,100 @@ +use cfg_types::tokens::CurrencyId; +use orml_traits::asset_registry::AssetMetadata; +use runtime_common::xcm::CurrencyIdConvert; +use sp_runtime::traits::Convert; +use staging_xcm::{ + v4::{Junction::*, Junctions::Here, Location}, + VersionedLocation, +}; + +use crate::generic::{ + config::Runtime, + env::Env, + envs::runtime_env::RuntimeEnv, + utils::{ + currency::{default_metadata, CurrencyInfo, CustomCurrency}, + genesis::{self, Genesis}, + xcm::transferable_custom, + }, +}; + +const PARA_ID: u32 = 1000; + +#[test_runtimes(all)] +fn convert_transferable_asset() { + // The way the native currency is represented relative to its runtime + let location_inner = Location::new(0, Here); + + // The canonical way the native currency is represented out in the wild + let location_canonical = Location::new(1, Parachain(PARA_ID)); + + let curr = CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals: 18, + location: Some(VersionedLocation::V4(location_canonical.clone())), + additional: transferable_custom(), + ..default_metadata() + }, + ); + + let env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::parachain_id::(PARA_ID)) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + env.parachain_state(|| { + assert_eq!( + CurrencyIdConvert::::convert(location_inner), + Some(curr.id()), + ); + + assert_eq!( + CurrencyIdConvert::::convert(curr.id()), + Some(location_canonical) + ) + }); +} + +#[test_runtimes(all)] +fn cannot_convert_nontransferable_asset() { + let curr = CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals: 18, + location: Some(VersionedLocation::V4(Location::new(1, Parachain(PARA_ID)))), + additional: Default::default(), // <- Not configured for transfers + ..default_metadata() + }, + ); + + let env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::parachain_id::(PARA_ID)) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + env.parachain_state(|| { + assert_eq!( + CurrencyIdConvert::::convert(Location::new(0, Here)), + Some(curr.id()), + ); + + assert_eq!(CurrencyIdConvert::::convert(curr.id()), None); + }); +} + +#[test_runtimes(all)] +fn convert_unknown_location() { + let env = RuntimeEnv::::default(); + + env.parachain_state(|| { + assert_eq!( + CurrencyIdConvert::::convert(Location::new(0, Here)), + None, + ); + }); +} diff --git a/runtime/integration-tests/src/generic/cases/investments.rs b/runtime/integration-tests/src/generic/cases/investments.rs index 644f386145..3f2869ff3e 100644 --- a/runtime/integration-tests/src/generic/cases/investments.rs +++ b/runtime/integration-tests/src/generic/cases/investments.rs @@ -43,7 +43,7 @@ mod common { .add(genesis::balances::( T::ExistentialDeposit::get() + FOR_FEES, )) - .add(genesis::assets::(vec![Box::new(Usd6)])) + .add(genesis::assets::(vec![(Usd6.id(), &Usd6.metadata())])) .add(genesis::tokens::(vec![(Usd6.id(), Usd6.ed())])) .storage(), ); diff --git a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs index 9383cede21..19985385f8 100644 --- a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs @@ -10,14 +10,11 @@ use cfg_types::{ domain_address::{Domain, DomainAddress}, fixed_point::{Quantity, Ratio}, investments::{InvestCollection, InvestmentAccount, RedeemCollection}, - locations::RestrictedTransferLocation, orders::FulfillmentWithPrice, permissions::{PermissionScope, PoolRole, Role}, pools::TrancheMetadata, tokens::{AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata}, - xcm::XcmMetadata, }; -use cfg_utils::vec_to_fixed_array; use frame_support::{ assert_noop, assert_ok, dispatch::RawOrigin, @@ -35,54 +32,34 @@ use pallet_investments::CollectOutcome; use pallet_liquidity_pools::Message; use pallet_liquidity_pools_gateway::{Call as LiquidityPoolsGatewayCall, GatewayOrigin}; use pallet_pool_system::tranches::{TrancheInput, TrancheLoc, TrancheType}; -use parity_scale_codec::Encode; use polkadot_core_primitives::BlakeTwo256; -use polkadot_parachain_primitives::primitives::{Id, ValidationCode}; -use polkadot_runtime_parachains::{ - paras, - paras::{ParaGenesisArgs, ParaKind}, -}; use runtime_common::{ - account_conversion::AccountConverter, - foreign_investments::IdentityPoolCurrencyConverter, + account_conversion::AccountConverter, foreign_investments::IdentityPoolCurrencyConverter, xcm::general_key, - xcm_fees::{default_per_second, ksm_per_second}, }; use sp_core::{Get, H160, U256}; use sp_runtime::{ - traits::{ - AccountIdConversion, BadOrigin, ConstU32, Convert as C1, Convert as C2, EnsureAdd, Hash, - One, StaticLookup, Zero, - }, - BoundedVec, BuildStorage, DispatchError, FixedPointNumber, Perquintill, SaturatedConversion, - WeakBoundedVec, + traits::{AccountIdConversion, BadOrigin, ConstU32, Convert, EnsureAdd, Hash, One, Zero}, + BoundedVec, DispatchError, FixedPointNumber, Perquintill, SaturatedConversion, }; use staging_xcm::{ - prelude::XCM_VERSION, - v4::{ - Asset, AssetId, Assets, Fungibility, Junction, Junction::*, Junctions, Junctions::*, - Location, NetworkId, WeightLimit, - }, - VersionedAsset, VersionedAssets, VersionedLocation, + v4::{Junction, Junction::*, Location, NetworkId}, + VersionedLocation, }; -use utils::*; use crate::{ generic::{ config::Runtime, env::{Blocks, Env}, - envs::fudge_env::{handle::FudgeHandle, FudgeEnv, FudgeRelayRuntime, FudgeSupport}, - utils::{democracy::execute_via_democracy, genesis, genesis::Genesis, xcm::setup_xcm}, + envs::fudge_env::{handle::SIBLING_ID, FudgeEnv, FudgeSupport}, + utils::{ + democracy::execute_via_democracy, genesis, genesis::Genesis, + xcm::enable_para_to_sibling_communication, + }, }, - utils::accounts::Keyring, + utils::{accounts::Keyring, orml_asset_registry}, }; -mod orml_asset_registry { - // orml_asset_registry has remove the reexport of all pallet stuff, - // we reexport it again here - pub use orml_asset_registry::module::*; -} - /// The AUSD asset id pub const AUSD_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(3); /// The USDT asset id @@ -91,50 +68,35 @@ pub const USDT_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1); pub const AUSD_ED: Balance = 1_000_000_000; pub const USDT_ED: Balance = 10_000; -pub mod utils { - use super::*; - - pub fn parachain_account(id: u32) -> AccountId { - polkadot_parachain_primitives::primitives::Sibling::from(id).into_account_truncating() - } - - pub fn xcm_metadata(transferability: CrossChainTransferability) -> Option { - match transferability { - CrossChainTransferability::Xcm(x) => Some(x), - _ => None, - } - } - - pub fn setup_usdc_xcm(env: &mut FudgeEnv) { - env.parachain_state_mut(|| { - // Set the XCM version used when sending XCM messages to USDC parachain. - assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new(1, Junction::Parachain(1000))), - XCM_VERSION, - )); - }); - - env.relay_state_mut(|| { - assert_ok!(polkadot_runtime_parachains::hrmp::Pallet::< - FudgeRelayRuntime, - >::force_open_hrmp_channel( - as frame_system::Config>::RuntimeOrigin::root(), - Id::from(T::FudgeHandle::PARA_ID), - Id::from(1000), - 10, - 1024, - )); +pub const GLMR_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(4); +pub const GLMR_ED: Balance = 1_000_000; +pub const DEFAULT_BALANCE_GLMR: Balance = 10_000_000_000_000_000_000; +pub const POOL_ADMIN: Keyring = Keyring::Bob; +pub const POOL_ID: PoolId = 42; +pub const MOONBEAM_EVM_CHAIN_ID: u64 = 1284; +pub const DEFAULT_EVM_ADDRESS_MOONBEAM: [u8; 20] = [99; 20]; +pub const DEFAULT_VALIDITY: Seconds = 2555583502; +pub const DOMAIN_MOONBEAM: Domain = Domain::EVM(MOONBEAM_EVM_CHAIN_ID); +pub const DEFAULT_DOMAIN_ADDRESS_MOONBEAM: DomainAddress = + DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, DEFAULT_EVM_ADDRESS_MOONBEAM); +pub const DEFAULT_OTHER_DOMAIN_ADDRESS: DomainAddress = + DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [0; 20]); + +pub type LiquidityPoolMessage = Message; + +mod utils { + use cfg_types::oracles::OracleKey; + use frame_support::weights::Weight; + use runtime_common::oracle::Feeder; - assert_ok!(polkadot_runtime_parachains::hrmp::Pallet::< - FudgeRelayRuntime, - >::force_process_hrmp_open( - as frame_system::Config>::RuntimeOrigin::root(), - 2, - )); - }); + use super::*; - env.pass(Blocks::ByNumber(1)); + /// Creates a new pool for the given id with + /// * BOB as admin and depositor + /// * Two tranches + /// * AUSD as pool currency with max reserve 10k. + pub fn create_ausd_pool(pool_id: u64) { + create_currency_pool::(pool_id, AUSD_CURRENCY_ID, decimals(currency_decimals::AUSD)) } pub fn register_ausd() { @@ -146,7 +108,7 @@ pub mod utils { location: Some(VersionedLocation::V4(Location::new( 1, [ - Parachain(T::FudgeHandle::SIBLING_ID), + Parachain(SIBLING_ID), general_key(parachains::kusama::karura::AUSD_KEY), ], ))), @@ -164,38 +126,14 @@ pub mod utils { )); } - pub fn ausd(amount: Balance) -> Balance { - amount * decimals(currency_decimals::AUSD) - } - - pub fn ausd_fee() -> Balance { - fee(currency_decimals::AUSD) - } - pub fn cfg(amount: Balance) -> Balance { amount * decimals(currency_decimals::NATIVE) } - pub fn cfg_fee() -> Balance { - fee(currency_decimals::NATIVE) - } - pub fn decimals(decimals: u32) -> Balance { 10u128.saturating_pow(decimals) } - pub fn fee(decimals: u32) -> Balance { - calc_fee(default_per_second(decimals)) - } - - pub fn calc_fee(fee_per_second: Balance) -> Balance { - // We divide the fee to align its unit and multiply by 4 as that seems to be the - // unit of time the tests take. - // NOTE: it is possible that in different machines this value may differ. We - // shall see. - fee_per_second.div_euclid(10_000) * 8 - } - pub fn set_domain_router_call( domain: Domain, router: DomainRouter, @@ -210,63 +148,26 @@ pub mod utils { pub fn remove_instance_call(instance: DomainAddress) -> T::RuntimeCallExt { LiquidityPoolsGatewayCall::remove_instance { instance }.into() } -} - -mod development { - use utils::*; - - use super::*; - - pub const GLMR_CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(4); - pub const GLMR_ED: Balance = 1_000_000; - pub const DEFAULT_BALANCE_GLMR: Balance = 10_000_000_000_000_000_000; - pub const POOL_ADMIN: Keyring = Keyring::Bob; - pub const POOL_ID: PoolId = 42; - pub const MOONBEAM_EVM_CHAIN_ID: u64 = 1284; - pub const DEFAULT_EVM_ADDRESS_MOONBEAM: [u8; 20] = [99; 20]; - pub const DEFAULT_VALIDITY: Seconds = 2555583502; - pub const DOMAIN_MOONBEAM: Domain = Domain::EVM(MOONBEAM_EVM_CHAIN_ID); - pub const DEFAULT_DOMAIN_ADDRESS_MOONBEAM: DomainAddress = - DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, DEFAULT_EVM_ADDRESS_MOONBEAM); - pub const DEFAULT_OTHER_DOMAIN_ADDRESS: DomainAddress = - DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [0; 20]); - - pub type LiquidityPoolMessage = Message; - - mod utils { - use cfg_types::oracles::OracleKey; - use frame_support::weights::Weight; - use runtime_common::oracle::Feeder; - - use super::*; - /// Creates a new pool for the given id with - /// * BOB as admin and depositor - /// * Two tranches - /// * AUSD as pool currency with max reserve 10k. - pub fn create_ausd_pool(pool_id: u64) { - create_currency_pool::(pool_id, AUSD_CURRENCY_ID, decimals(currency_decimals::AUSD)) - } - - /// Creates a new pool for for the given id with the provided currency. - /// * BOB as admin and depositor - /// * Two tranches - /// * The given `currency` as pool currency with of - /// `currency_decimals`. - pub fn create_currency_pool( - pool_id: u64, - currency_id: CurrencyId, - currency_decimals: Balance, - ) { - assert_ok!(pallet_pool_system::Pallet::::create( - POOL_ADMIN.into(), - POOL_ADMIN.into(), - pool_id, - vec![ - TrancheInput { - tranche_type: TrancheType::Residual, - seniority: None, - metadata: TrancheMetadata { + /// Creates a new pool for for the given id with the provided currency. + /// * BOB as admin and depositor + /// * Two tranches + /// * The given `currency` as pool currency with of `currency_decimals`. + pub fn create_currency_pool( + pool_id: u64, + currency_id: CurrencyId, + currency_decimals: Balance, + ) { + assert_ok!(pallet_pool_system::Pallet::::create( + POOL_ADMIN.into(), + POOL_ADMIN.into(), + pool_id, + vec![ + TrancheInput { + tranche_type: TrancheType::Residual, + seniority: None, + metadata: + TrancheMetadata { // NOTE: For now, we have to set these metadata fields of the first // tranche to be convertible to the 32-byte size expected by the // liquidity pools AddTranche message. @@ -283,376 +184,269 @@ mod development { >::try_from("TrNcH".as_bytes().to_vec()) .expect("Can create BoundedVec for token symbol"), } + }, + TrancheInput { + tranche_type: TrancheType::NonResidual { + interest_rate_per_sec: One::one(), + min_risk_buffer: Perquintill::from_percent(10), }, - TrancheInput { - tranche_type: TrancheType::NonResidual { - interest_rate_per_sec: One::one(), - min_risk_buffer: Perquintill::from_percent(10), - }, - seniority: None, - metadata: TrancheMetadata { - token_name: BoundedVec::default(), - token_symbol: BoundedVec::default(), - } + seniority: None, + metadata: TrancheMetadata { + token_name: BoundedVec::default(), + token_symbol: BoundedVec::default(), } - ], - currency_id, - currency_decimals, - // No pool fees per default - vec![] - )); - } + } + ], + currency_id, + currency_decimals, + // No pool fees per default + vec![] + )); + } - pub fn register_glmr() { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: GLMR_ED, - location: Some(VersionedLocation::V4(Location::new( - 1, - [Parachain(T::FudgeHandle::SIBLING_ID), general_key(&[0, 1])], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + pub fn register_glmr() { + let meta: AssetMetadata = AssetMetadata { + decimals: 18, + name: BoundedVec::default(), + symbol: BoundedVec::default(), + existential_deposit: GLMR_ED, + location: Some(VersionedLocation::V4(Location::new( + 1, + [Parachain(SIBLING_ID), general_key(&[0, 1])], + ))), + additional: CustomMetadata { + transferability: CrossChainTransferability::Xcm(Default::default()), + ..CustomMetadata::default() + }, + }; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(GLMR_CURRENCY_ID) - )); - } + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + meta, + Some(GLMR_CURRENCY_ID) + )); + } - pub fn set_test_domain_router( - evm_chain_id: u64, - xcm_domain_location: VersionedLocation, - currency_id: CurrencyId, - ) { - let ethereum_xcm_router = EthereumXCMRouter:: { - router: XCMRouter { - xcm_domain: XcmDomain { - location: Box::new(xcm_domain_location), - ethereum_xcm_transact_call_index: BoundedVec::truncate_from(vec![38, 0]), - contract_address: H160::from(DEFAULT_EVM_ADDRESS_MOONBEAM), - max_gas_limit: 500_000, - transact_required_weight_at_most: Weight::from_parts( - 12530000000, - DEFAULT_PROOF_SIZE.saturating_div(2), - ), - overall_weight: Weight::from_parts(15530000000, DEFAULT_PROOF_SIZE), - fee_currency: currency_id, - // 0.2 token - fee_amount: 200000000000000000, - }, - _marker: Default::default(), + pub fn set_test_domain_router( + evm_chain_id: u64, + xcm_domain_location: VersionedLocation, + currency_id: CurrencyId, + ) { + let ethereum_xcm_router = EthereumXCMRouter:: { + router: XCMRouter { + xcm_domain: XcmDomain { + location: Box::new(xcm_domain_location), + ethereum_xcm_transact_call_index: BoundedVec::truncate_from(vec![38, 0]), + contract_address: H160::from(DEFAULT_EVM_ADDRESS_MOONBEAM), + max_gas_limit: 500_000, + transact_required_weight_at_most: Weight::from_parts( + 12530000000, + DEFAULT_PROOF_SIZE.saturating_div(2), + ), + overall_weight: Weight::from_parts(15530000000, DEFAULT_PROOF_SIZE), + fee_currency: currency_id, + // 0.2 token + fee_amount: 200000000000000000, }, _marker: Default::default(), - }; - - let domain_router = DomainRouter::EthereumXCM(ethereum_xcm_router); - let domain = Domain::EVM(evm_chain_id); - - assert_ok!( - pallet_liquidity_pools_gateway::Pallet::::set_domain_router( - ::RuntimeOrigin::root(), - domain, - domain_router, - ) - ); - } - - pub fn default_tranche_id(pool_id: u64) -> TrancheId { - let pool_details = - pallet_pool_system::pallet::Pool::::get(pool_id).expect("Pool should exist"); - pool_details - .tranches - .tranche_id(TrancheLoc::Index(0)) - .expect("Tranche at index 0 exists") - } - - /// Returns a `VersionedLocation` that can be converted into - /// `LiquidityPoolsWrappedToken` which is required for cross chain asset - /// registration and transfer. - pub fn liquidity_pools_transferable_multilocation( - chain_id: u64, - address: [u8; 20], - ) -> VersionedLocation { - VersionedLocation::V4(Location::new( - 0, - [ - PalletInstance( - ::PalletInfo::index::< - pallet_liquidity_pools::Pallet, - >() - .expect("LiquidityPools should have pallet index") - .saturated_into(), - ), - GlobalConsensus(NetworkId::Ethereum { chain_id }), - AccountKey20 { - network: None, - key: address, - }, - ], - )) - } + }, + _marker: Default::default(), + }; - /// Enables `LiquidityPoolsTransferable` in the custom asset metadata - /// for the given currency_id. - /// - /// NOTE: Sets the location to the `MOONBEAM_EVM_CHAIN_ID` with dummy - /// address as the location is required for LiquidityPoolsWrappedToken - /// conversions. - pub fn enable_liquidity_pool_transferability( - currency_id: CurrencyId, - ) { - let metadata = orml_asset_registry::Metadata::::get(currency_id) - .expect("Currency should be registered"); - let location = Some(Some(liquidity_pools_transferable_multilocation::( - MOONBEAM_EVM_CHAIN_ID, - // Value of evm_address is irrelevant here - [1u8; 20], - ))); + let domain_router = DomainRouter::EthereumXCM(ethereum_xcm_router); + let domain = Domain::EVM(evm_chain_id); - assert_ok!(orml_asset_registry::Pallet::::update_asset( + assert_ok!( + pallet_liquidity_pools_gateway::Pallet::::set_domain_router( ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - location, - Some(CustomMetadata { - // Changed: Allow liquidity_pools transferability - transferability: CrossChainTransferability::LiquidityPools, - ..metadata.additional - }) - )); - } - - pub fn setup_test(env: &mut FudgeEnv) { - setup_xcm(env); - - env.parachain_state_mut(|| { - register_ausd::(); - register_glmr::(); - - assert_ok!(orml_tokens::Pallet::::set_balance( - ::RuntimeOrigin::root(), - ::Sender::get().into(), - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - 0, - )); - - set_test_domain_router::( - MOONBEAM_EVM_CHAIN_ID, - Location::new(1, Junction::Parachain(T::FudgeHandle::SIBLING_ID)).into(), - GLMR_CURRENCY_ID, - ); - }); - } - - /// Returns the derived general currency index. - /// - /// Throws if the provided currency_id is not - /// `CurrencyId::ForeignAsset(id)`. - pub fn general_currency_index(currency_id: CurrencyId) -> u128 { - pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) - .expect("ForeignAsset should convert into u128") - } + domain, + domain_router, + ) + ); + } - /// Returns the investment_id of the given pool and tranche ids. - pub fn investment_id( - pool_id: u64, - tranche_id: TrancheId, - ) -> cfg_types::tokens::TrancheCurrency { - ::TrancheCurrency::generate(pool_id, tranche_id) - } + pub fn default_tranche_id(pool_id: u64) -> TrancheId { + let pool_details = + pallet_pool_system::pallet::Pool::::get(pool_id).expect("Pool should exist"); + pool_details + .tranches + .tranche_id(TrancheLoc::Index(0)) + .expect("Tranche at index 0 exists") + } - pub fn default_investment_id( - ) -> cfg_types::tokens::TrancheCurrency { - ::TrancheCurrency::generate( - POOL_ID, - default_tranche_id::(POOL_ID), - ) - } + /// Returns a `VersionedLocation` that can be converted into + /// `LiquidityPoolsWrappedToken` which is required for cross chain asset + /// registration and transfer. + pub fn liquidity_pools_transferable_multilocation( + chain_id: u64, + address: [u8; 20], + ) -> VersionedLocation { + VersionedLocation::V4(Location::new( + 0, + [ + PalletInstance( + ::PalletInfo::index::< + pallet_liquidity_pools::Pallet, + >() + .expect("LiquidityPools should have pallet index") + .saturated_into(), + ), + GlobalConsensus(NetworkId::Ethereum { chain_id }), + AccountKey20 { + network: None, + key: address, + }, + ], + )) + } - pub fn default_order_id(investor: &AccountId) -> OrderId { - let default_swap_id = ( - default_investment_id::(), - pallet_foreign_investments::Action::Investment, - ); - pallet_swaps::Pallet::::order_id(&investor, default_swap_id) - .expect("Swap order exists; qed") - } + /// Enables `LiquidityPoolsTransferable` in the custom asset metadata + /// for the given currency_id. + /// + /// NOTE: Sets the location to the `MOONBEAM_EVM_CHAIN_ID` with dummy + /// address as the location is required for LiquidityPoolsWrappedToken + /// conversions. + pub fn enable_liquidity_pool_transferability( + currency_id: CurrencyId, + ) { + let metadata = orml_asset_registry::Metadata::::get(currency_id) + .expect("Currency should be registered"); + let location = Some(Some(liquidity_pools_transferable_multilocation::( + MOONBEAM_EVM_CHAIN_ID, + // Value of evm_address is irrelevant here + [1u8; 20], + ))); + + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + location, + Some(CustomMetadata { + // Changed: Allow liquidity_pools transferability + transferability: CrossChainTransferability::LiquidityPools, + ..metadata.additional + }) + )); + } - /// Returns the default investment account derived from the - /// `DEFAULT_POOL_ID` and its default tranche. - pub fn default_investment_account() -> AccountId { - InvestmentAccount { - investment_id: default_investment_id::(), - } - .into_account_truncating() - } + pub fn setup_test(env: &mut FudgeEnv) { + env.parachain_state_mut(|| { + register_ausd::(); + register_glmr::(); - pub fn fulfill_swap_into_pool( - pool_id: u64, - swap_order_id: u64, - amount_pool: Balance, - amount_foreign: Balance, - trader: AccountId, - ) { - let pool_currency: CurrencyId = pallet_pool_system::Pallet::::currency_for(pool_id) - .expect("Pool existence checked already"); - assert_ok!(orml_tokens::Pallet::::mint_into( - pool_currency, - &trader, - amount_pool - )); - assert_ok!(pallet_order_book::Pallet::::fill_order( - RawOrigin::Signed(trader.clone()).into(), - swap_order_id, - amount_foreign + assert_ok!(orml_tokens::Pallet::::set_balance( + ::RuntimeOrigin::root(), + ::Sender::get().into(), + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + 0, )); - } - /// Sets up required permissions for the investor and executes an - /// initial investment via LiquidityPools by executing - /// `IncreaseInvestOrder`. - /// - /// Assumes `setup_pre_requirements` and - /// `investments::create_currency_pool` to have been called - /// beforehand - pub fn do_initial_increase_investment( - pool_id: u64, - amount: Balance, - investor: AccountId, - currency_id: CurrencyId, - ) { - let pool_currency: CurrencyId = pallet_pool_system::Pallet::::currency_for(pool_id) - .expect("Pool existence checked already"); - - // Mock incoming increase invest message - let msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount, - }; + set_test_domain_router::( + MOONBEAM_EVM_CHAIN_ID, + Location::new(1, Junction::Parachain(SIBLING_ID)).into(), + GLMR_CURRENCY_ID, + ); + }); + } - // Should fail if investor does not have investor role yet - // However, failure is async for foreign currencies as part of updating the - // investment after the swap was fulfilled - if currency_id == pool_currency { - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg.clone() - ), - DispatchError::Other("Account does not have the TrancheInvestor permission.") - ); - } + /// Returns the derived general currency index. + /// + /// Throws if the provided currency_id is not + /// `CurrencyId::ForeignAsset(id)`. + pub fn general_currency_index(currency_id: CurrencyId) -> u128 { + pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) + .expect("ForeignAsset should convert into u128") + } - // Make investor the MembersListAdmin of this Pool - if !pallet_permissions::Pallet::::has( - PermissionScope::Pool(pool_id), - investor.clone(), - Role::PoolRole(PoolRole::TrancheInvestor( - default_tranche_id::(pool_id), - DEFAULT_VALIDITY, - )), - ) { - crate::generic::utils::pool::give_role::( - investor.clone(), - pool_id, - PoolRole::TrancheInvestor(default_tranche_id::(pool_id), DEFAULT_VALIDITY), - ); - } + /// Returns the investment_id of the given pool and tranche ids. + pub fn investment_id( + pool_id: u64, + tranche_id: TrancheId, + ) -> cfg_types::tokens::TrancheCurrency { + ::TrancheCurrency::generate(pool_id, tranche_id) + } - let amount_before = - orml_tokens::Pallet::::balance(currency_id, &default_investment_account::()); - let final_amount = amount_before - .ensure_add(amount) - .expect("Should not overflow when incrementing amount"); + pub fn default_investment_id() -> cfg_types::tokens::TrancheCurrency + { + ::TrancheCurrency::generate( + POOL_ID, + default_tranche_id::(POOL_ID), + ) + } - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); + pub fn default_order_id(investor: &AccountId) -> OrderId { + let default_swap_id = ( + default_investment_id::(), + pallet_foreign_investments::Action::Investment, + ); + pallet_swaps::Pallet::::order_id(&investor, default_swap_id) + .expect("Swap order exists; qed") + } - if currency_id == pool_currency { - // Verify investment was transferred into investment account - assert_eq!( - orml_tokens::Pallet::::balance( - currency_id, - &default_investment_account::() - ), - final_amount - ); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::InvestOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: final_amount, - } - .into() - })); - } + /// Returns the default investment account derived from the + /// `DEFAULT_POOL_ID` and its default tranche. + pub fn default_investment_account() -> AccountId { + InvestmentAccount { + investment_id: default_investment_id::(), } + .into_account_truncating() + } - /// Sets up required permissions for the investor and executes an - /// initial redemption via LiquidityPools by executing - /// `IncreaseRedeemOrder`. - /// - /// Assumes `setup_pre_requirements` and - /// `investments::create_currency_pool` to have been called - /// beforehand. - /// - /// NOTE: Mints exactly the redeeming amount of tranche tokens. - pub fn do_initial_increase_redemption( - pool_id: u64, - amount: Balance, - investor: AccountId, - currency_id: CurrencyId, - ) { - // Fund `DomainLocator` account of origination domain as redeemed tranche tokens - // are transferred from this account instead of minting - assert_ok!(orml_tokens::Pallet::::mint_into( - default_investment_id::().into(), - &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), - amount - )); - - // Verify redemption has not been made yet - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &default_investment_account::(), - ), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::balance(default_investment_id::().into(), &investor), - 0 - ); + pub fn fulfill_swap_into_pool( + pool_id: u64, + swap_order_id: u64, + amount_pool: Balance, + amount_foreign: Balance, + trader: AccountId, + ) { + let pool_currency: CurrencyId = pallet_pool_system::Pallet::::currency_for(pool_id) + .expect("Pool existence checked already"); + assert_ok!(orml_tokens::Pallet::::mint_into( + pool_currency, + &trader, + amount_pool + )); + assert_ok!(pallet_order_book::Pallet::::fill_order( + RawOrigin::Signed(trader.clone()).into(), + swap_order_id, + amount_foreign + )); + } - // Mock incoming increase invest message - let msg = LiquidityPoolMessage::IncreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount, - }; + /// Sets up required permissions for the investor and executes an + /// initial investment via LiquidityPools by executing + /// `IncreaseInvestOrder`. + /// + /// Assumes `setup_pre_requirements` and + /// `investments::create_currency_pool` to have been called + /// beforehand + pub fn do_initial_increase_investment( + pool_id: u64, + amount: Balance, + investor: AccountId, + currency_id: CurrencyId, + ) { + let pool_currency: CurrencyId = pallet_pool_system::Pallet::::currency_for(pool_id) + .expect("Pool existence checked already"); + + // Mock incoming increase invest message + let msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount, + }; - // Should fail if investor does not have investor role yet + // Should fail if investor does not have investor role yet + // However, failure is async for foreign currencies as part of updating the + // investment after the swap was fulfilled + if currency_id == pool_currency { assert_noop!( pallet_liquidity_pools::Pallet::::submit( DEFAULT_DOMAIN_ADDRESS_MOONBEAM, @@ -660,7140 +454,4029 @@ mod development { ), DispatchError::Other("Account does not have the TrancheInvestor permission.") ); + } - // Make investor the MembersListAdmin of this Pool + // Make investor the MembersListAdmin of this Pool + if !pallet_permissions::Pallet::::has( + PermissionScope::Pool(pool_id), + investor.clone(), + Role::PoolRole(PoolRole::TrancheInvestor( + default_tranche_id::(pool_id), + DEFAULT_VALIDITY, + )), + ) { crate::generic::utils::pool::give_role::( investor.clone(), pool_id, PoolRole::TrancheInvestor(default_tranche_id::(pool_id), DEFAULT_VALIDITY), ); + } - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); + let amount_before = + orml_tokens::Pallet::::balance(currency_id, &default_investment_account::()); + let final_amount = amount_before + .ensure_add(amount) + .expect("Should not overflow when incrementing amount"); - // Verify redemption was transferred into investment account - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &default_investment_account::(), - ), - amount - ); - assert_eq!( - orml_tokens::Pallet::::balance(default_investment_id::().into(), &investor), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &AccountConverter::convert(DEFAULT_OTHER_DOMAIN_ADDRESS) - ), - 0 - ); - assert_eq!( - frame_system::Pallet::::events() - .iter() - .last() - .unwrap() - .event, - pallet_investments::Event::::RedeemOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor, - amount - } - .into() - ); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - // Verify order id is 0 + if currency_id == pool_currency { + // Verify investment was transferred into investment account assert_eq!( - pallet_investments::Pallet::::redeem_order_id(investment_id::( - pool_id, - default_tranche_id::(pool_id) - )), - 0 + orml_tokens::Pallet::::balance(currency_id, &default_investment_account::()), + final_amount ); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::InvestOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: final_amount, + } + .into() + })); } + } - /// Register USDT in the asset registry and enable LiquidityPools cross - /// chain transferability. - /// - /// NOTE: Assumes to be executed within an externalities environment. - fn register_usdt() { - let meta: AssetMetadata = AssetMetadata { - decimals: 6, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: USDT_ED, - location: Some(VersionedLocation::V4(Location::new( - 1, - [Parachain(1000), PalletInstance(50), GeneralIndex(1984)], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::LiquidityPools, - pool_currency: true, - ..CustomMetadata::default() - }, - }; + /// Sets up required permissions for the investor and executes an + /// initial redemption via LiquidityPools by executing + /// `IncreaseRedeemOrder`. + /// + /// Assumes `setup_pre_requirements` and + /// `investments::create_currency_pool` to have been called + /// beforehand. + /// + /// NOTE: Mints exactly the redeeming amount of tranche tokens. + pub fn do_initial_increase_redemption( + pool_id: u64, + amount: Balance, + investor: AccountId, + currency_id: CurrencyId, + ) { + // Fund `DomainLocator` account of origination domain as redeemed tranche tokens + // are transferred from this account instead of minting + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), + amount + )); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(USDT_CURRENCY_ID) - )); - } + // Verify redemption has not been made yet + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &default_investment_account::(), + ), + 0 + ); + assert_eq!( + orml_tokens::Pallet::::balance(default_investment_id::().into(), &investor), + 0 + ); + + // Mock incoming increase invest message + let msg = LiquidityPoolMessage::IncreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount, + }; - /// Registers USDT currency, adds bidirectional trading pairs with - /// conversion ratio one and returns the amount in foreign denomination. - pub fn enable_usdt_trading( - pool_currency: CurrencyId, - amount_pool_denominated: Balance, - enable_lp_transferability: bool, - enable_foreign_to_pool_pair: bool, - enable_pool_to_foreign_pair: bool, - ) -> Balance { - register_usdt::(); - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount_foreign_denominated: u128 = - IdentityPoolCurrencyConverter::>::stable_to_stable( - foreign_currency, - pool_currency, - amount_pool_denominated, - ) - .unwrap(); + // Should fail if investor does not have investor role yet + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg.clone() + ), + DispatchError::Other("Account does not have the TrancheInvestor permission.") + ); + + // Make investor the MembersListAdmin of this Pool + crate::generic::utils::pool::give_role::( + investor.clone(), + pool_id, + PoolRole::TrancheInvestor(default_tranche_id::(pool_id), DEFAULT_VALIDITY), + ); + + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - if enable_lp_transferability { - enable_liquidity_pool_transferability::(foreign_currency); + // Verify redemption was transferred into investment account + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &default_investment_account::(), + ), + amount + ); + assert_eq!( + orml_tokens::Pallet::::balance(default_investment_id::().into(), &investor), + 0 + ); + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &AccountConverter::convert(DEFAULT_OTHER_DOMAIN_ADDRESS) + ), + 0 + ); + assert_eq!( + frame_system::Pallet::::events() + .iter() + .last() + .unwrap() + .event, + pallet_investments::Event::::RedeemOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor, + amount } + .into() + ); - assert_ok!(pallet_order_book::Pallet::::set_market_feeder( - ::RuntimeOrigin::root(), - Feeder::root(), - )); - crate::generic::utils::oracle::update_feeders::( - POOL_ADMIN.id(), - POOL_ID, - [Feeder::root()], - ); + // Verify order id is 0 + assert_eq!( + pallet_investments::Pallet::::redeem_order_id(investment_id::( + pool_id, + default_tranche_id::(pool_id) + )), + 0 + ); + } - if enable_foreign_to_pool_pair { - crate::generic::utils::oracle::feed_from_root::( - OracleKey::ConversionRatio(foreign_currency, pool_currency), - Ratio::one(), - ); - } - if enable_pool_to_foreign_pair { - crate::generic::utils::oracle::feed_from_root::( - OracleKey::ConversionRatio(pool_currency, foreign_currency), - Ratio::one(), - ); - } + /// Register USDT in the asset registry and enable LiquidityPools cross + /// chain transferability. + /// + /// NOTE: Assumes to be executed within an externalities environment. + fn register_usdt() { + let meta: AssetMetadata = AssetMetadata { + decimals: 6, + name: BoundedVec::default(), + symbol: BoundedVec::default(), + existential_deposit: USDT_ED, + location: Some(VersionedLocation::V4(Location::new( + 1, + [Parachain(1000), PalletInstance(50), GeneralIndex(1984)], + ))), + additional: CustomMetadata { + transferability: CrossChainTransferability::LiquidityPools, + pool_currency: true, + ..CustomMetadata::default() + }, + }; + + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + meta, + Some(USDT_CURRENCY_ID) + )); + } + + /// Registers USDT currency, adds bidirectional trading pairs with + /// conversion ratio one and returns the amount in foreign denomination. + pub fn enable_usdt_trading( + pool_currency: CurrencyId, + amount_pool_denominated: Balance, + enable_lp_transferability: bool, + enable_foreign_to_pool_pair: bool, + enable_pool_to_foreign_pair: bool, + ) -> Balance { + register_usdt::(); + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount_foreign_denominated: u128 = + IdentityPoolCurrencyConverter::>::stable_to_stable( + foreign_currency, + pool_currency, + amount_pool_denominated, + ) + .unwrap(); - amount_foreign_denominated + if enable_lp_transferability { + enable_liquidity_pool_transferability::(foreign_currency); } - pub fn get_council_members() -> Vec { - vec![Keyring::Alice, Keyring::Bob, Keyring::Charlie] + assert_ok!(pallet_order_book::Pallet::::set_market_feeder( + ::RuntimeOrigin::root(), + Feeder::root(), + )); + crate::generic::utils::oracle::update_feeders::( + POOL_ADMIN.id(), + POOL_ID, + [Feeder::root()], + ); + + if enable_foreign_to_pool_pair { + crate::generic::utils::oracle::feed_from_root::( + OracleKey::ConversionRatio(foreign_currency, pool_currency), + Ratio::one(), + ); + } + if enable_pool_to_foreign_pair { + crate::generic::utils::oracle::feed_from_root::( + OracleKey::ConversionRatio(pool_currency, foreign_currency), + Ratio::one(), + ); } - } - mod add_allow_upgrade { - use cfg_types::tokens::LiquidityPoolsWrappedToken; + amount_foreign_denominated + } - use super::*; + pub fn get_council_members() -> Vec { + vec![Keyring::Alice, Keyring::Bob, Keyring::Charlie] + } +} - #[test_runtimes([development])] - fn add_pool() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); +use utils::*; - setup_test(&mut env); +mod add_allow_upgrade { + use cfg_types::tokens::LiquidityPoolsWrappedToken; - env.parachain_state_mut(|| { - let pool_id = POOL_ID; + use super::*; - // Verify that the pool must exist before we can call - // pallet_liquidity_pools::Pallet::::add_pool - assert_noop!( - pallet_liquidity_pools::Pallet::::add_pool( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::PoolNotFound - ); + #[test_runtimes([development])] + fn add_pool() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - // Now create the pool - create_ausd_pool::(pool_id); + setup_test(&mut env); - // Verify ALICE can't call `add_pool` given she is not the `PoolAdmin` - assert_noop!( - pallet_liquidity_pools::Pallet::::add_pool( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; - // Verify that it works if it's BOB calling it (the pool admin) - assert_ok!(pallet_liquidity_pools::Pallet::::add_pool( - RawOrigin::Signed(POOL_ADMIN.into()).into(), + // Verify that the pool must exist before we can call + // pallet_liquidity_pools::Pallet::::add_pool + assert_noop!( + pallet_liquidity_pools::Pallet::::add_pool( + RawOrigin::Signed(Keyring::Alice.into()).into(), pool_id, Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - )); - }); - } + ), + pallet_liquidity_pools::Error::::PoolNotFound + ); - #[test_runtimes([development])] - fn add_tranche() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), + // Now create the pool + create_ausd_pool::(pool_id); + + // Verify ALICE can't call `add_pool` given she is not the `PoolAdmin` + assert_noop!( + pallet_liquidity_pools::Pallet::::add_pool( + RawOrigin::Signed(Keyring::Alice.into()).into(), + pool_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ), + pallet_liquidity_pools::Error::::NotPoolAdmin ); - setup_test(&mut env); + // Verify that it works if it's BOB calling it (the pool admin) + assert_ok!(pallet_liquidity_pools::Pallet::::add_pool( + RawOrigin::Signed(POOL_ADMIN.into()).into(), + pool_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + )); + }); + } - env.parachain_state_mut(|| { - // Now create the pool - let pool_id = POOL_ID; - create_ausd_pool::(pool_id); + #[test_runtimes([development])] + fn add_tranche() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - // Verify we can't call pallet_liquidity_pools::Pallet::::add_tranche with a - // non-existing tranche_id - let nonexistent_tranche = [71u8; 16]; + setup_test(&mut env); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_tranche( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - nonexistent_tranche, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::TrancheNotFound - ); - let tranche_id = default_tranche_id::(pool_id); + env.parachain_state_mut(|| { + // Now create the pool + let pool_id = POOL_ID; + create_ausd_pool::(pool_id); - // Verify ALICE can't call `add_tranche` given she is not the `PoolAdmin` - assert_noop!( - pallet_liquidity_pools::Pallet::::add_tranche( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - tranche_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); + // Verify we can't call pallet_liquidity_pools::Pallet::::add_tranche with a + // non-existing tranche_id + let nonexistent_tranche = [71u8; 16]; - // Finally, verify we can call pallet_liquidity_pools::Pallet::::add_tranche - // successfully when called by the PoolAdmin with the right pool + tranche id - // pair. - assert_ok!(pallet_liquidity_pools::Pallet::::add_tranche( - RawOrigin::Signed(POOL_ADMIN.into()).into(), + assert_noop!( + pallet_liquidity_pools::Pallet::::add_tranche( + RawOrigin::Signed(Keyring::Alice.into()).into(), + pool_id, + nonexistent_tranche, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ), + pallet_liquidity_pools::Error::::TrancheNotFound + ); + let tranche_id = default_tranche_id::(pool_id); + + // Verify ALICE can't call `add_tranche` given she is not the `PoolAdmin` + assert_noop!( + pallet_liquidity_pools::Pallet::::add_tranche( + RawOrigin::Signed(Keyring::Alice.into()).into(), pool_id, tranche_id, Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - )); + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); - // Edge case: Should throw if tranche exists but metadata does not exist - let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_id); + // Finally, verify we can call pallet_liquidity_pools::Pallet::::add_tranche + // successfully when called by the PoolAdmin with the right pool + tranche id + // pair. + assert_ok!(pallet_liquidity_pools::Pallet::::add_tranche( + RawOrigin::Signed(POOL_ADMIN.into()).into(), + pool_id, + tranche_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + )); - orml_asset_registry::Metadata::::remove(tranche_currency_id); + // Edge case: Should throw if tranche exists but metadata does not exist + let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_id); - assert_noop!( - pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( - RawOrigin::Signed(POOL_ADMIN.into()).into(), - pool_id, - tranche_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::TrancheMetadataNotFound - ); - }); - } + orml_asset_registry::Metadata::::remove(tranche_currency_id); - #[test_runtimes([development])] - fn update_member() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), + assert_noop!( + pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( + RawOrigin::Signed(POOL_ADMIN.into()).into(), + pool_id, + tranche_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ), + pallet_liquidity_pools::Error::::TrancheMetadataNotFound ); + }); + } - setup_test(&mut env); + #[test_runtimes([development])] + fn update_member() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.parachain_state_mut(|| { - // Now create the pool - let pool_id = POOL_ID; + setup_test(&mut env); - create_ausd_pool::(pool_id); + env.parachain_state_mut(|| { + // Now create the pool + let pool_id = POOL_ID; - let tranche_id = default_tranche_id::(pool_id); + create_ausd_pool::(pool_id); - // Finally, verify we can call pallet_liquidity_pools::Pallet::::add_tranche - // successfully when given a valid pool + tranche id pair. - let new_member = DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [3; 20]); + let tranche_id = default_tranche_id::(pool_id); - // Make ALICE the MembersListAdmin of this Pool - assert_ok!(pallet_permissions::Pallet::::add( - ::RuntimeOrigin::root(), - Role::PoolRole(PoolRole::PoolAdmin), - Keyring::Alice.into(), - PermissionScope::Pool(pool_id), - Role::PoolRole(PoolRole::InvestorAdmin), - )); + // Finally, verify we can call pallet_liquidity_pools::Pallet::::add_tranche + // successfully when given a valid pool + tranche id pair. + let new_member = DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [3; 20]); - // Verify it fails if the destination is not whitelisted yet - assert_noop!( - pallet_liquidity_pools::Pallet::::update_member( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - tranche_id, - new_member.clone(), - DEFAULT_VALIDITY, - ), - pallet_liquidity_pools::Error::::InvestorDomainAddressNotAMember, - ); + // Make ALICE the MembersListAdmin of this Pool + assert_ok!(pallet_permissions::Pallet::::add( + ::RuntimeOrigin::root(), + Role::PoolRole(PoolRole::PoolAdmin), + Keyring::Alice.into(), + PermissionScope::Pool(pool_id), + Role::PoolRole(PoolRole::InvestorAdmin), + )); - // Whitelist destination as TrancheInvestor of this Pool - crate::generic::utils::pool::give_role::( - AccountConverter::convert(new_member.clone()), + // Verify it fails if the destination is not whitelisted yet + assert_noop!( + pallet_liquidity_pools::Pallet::::update_member( + RawOrigin::Signed(Keyring::Alice.into()).into(), pool_id, - PoolRole::TrancheInvestor(default_tranche_id::(pool_id), DEFAULT_VALIDITY), - ); + tranche_id, + new_member.clone(), + DEFAULT_VALIDITY, + ), + pallet_liquidity_pools::Error::::InvestorDomainAddressNotAMember, + ); - // Verify the Investor role was set as expected in Permissions - assert!(pallet_permissions::Pallet::::has( - PermissionScope::Pool(pool_id), - AccountConverter::convert(new_member.clone()), - Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, DEFAULT_VALIDITY)), - )); + // Whitelist destination as TrancheInvestor of this Pool + crate::generic::utils::pool::give_role::( + AccountConverter::convert(new_member.clone()), + pool_id, + PoolRole::TrancheInvestor(default_tranche_id::(pool_id), DEFAULT_VALIDITY), + ); - // Verify it now works - assert_ok!(pallet_liquidity_pools::Pallet::::update_member( + // Verify the Investor role was set as expected in Permissions + assert!(pallet_permissions::Pallet::::has( + PermissionScope::Pool(pool_id), + AccountConverter::convert(new_member.clone()), + Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, DEFAULT_VALIDITY)), + )); + + // Verify it now works + assert_ok!(pallet_liquidity_pools::Pallet::::update_member( + RawOrigin::Signed(Keyring::Alice.into()).into(), + pool_id, + tranche_id, + new_member, + DEFAULT_VALIDITY, + )); + + // Verify it cannot be called for another member without whitelisting the domain + // beforehand + assert_noop!( + pallet_liquidity_pools::Pallet::::update_member( RawOrigin::Signed(Keyring::Alice.into()).into(), pool_id, tranche_id, - new_member, + DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [9; 20]), DEFAULT_VALIDITY, - )); - - // Verify it cannot be called for another member without whitelisting the domain - // beforehand - assert_noop!( - pallet_liquidity_pools::Pallet::::update_member( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - tranche_id, - DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [9; 20]), - DEFAULT_VALIDITY, - ), - pallet_liquidity_pools::Error::::InvestorDomainAddressNotAMember, - ); - }); - } - - #[test_runtimes([development])] - fn update_token_price() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), + ), + pallet_liquidity_pools::Error::::InvestorDomainAddressNotAMember, ); + }); + } - setup_test(&mut env); + #[test_runtimes([development])] + fn update_token_price() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.parachain_state_mut(|| { - let currency_id = AUSD_CURRENCY_ID; - let pool_id = POOL_ID; + setup_test(&mut env); - enable_liquidity_pool_transferability::(currency_id); + env.parachain_state_mut(|| { + let currency_id = AUSD_CURRENCY_ID; + let pool_id = POOL_ID; - create_ausd_pool::(pool_id); + enable_liquidity_pool_transferability::(currency_id); - assert_ok!(pallet_liquidity_pools::Pallet::::update_token_price( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - default_tranche_id::(pool_id), - currency_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - )); - }); - } + create_ausd_pool::(pool_id); - #[test_runtimes([development])] - fn add_currency() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); + assert_ok!(pallet_liquidity_pools::Pallet::::update_token_price( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + default_tranche_id::(pool_id), + currency_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + )); + }); + } - setup_test(&mut env); + #[test_runtimes([development])] + fn add_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - let (domain, sender, message) = env.parachain_state_mut(|| { - let gateway_sender = ::Sender::get(); + setup_test(&mut env); - let currency_id = AUSD_CURRENCY_ID; + env.parachain_state_mut(|| { + let gateway_sender = ::Sender::get(); - enable_liquidity_pool_transferability::(currency_id); + let currency_id = AUSD_CURRENCY_ID; - assert_eq!( - orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), - DEFAULT_BALANCE_GLMR - ); - - assert_ok!(pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id, - )); - - let currency_index = - pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) - .expect("can get general index for currency"); - - let LiquidityPoolsWrappedToken::EVM { - address: evm_address, - .. - } = pallet_liquidity_pools::Pallet::::try_get_wrapped_token(¤cy_id) - .expect("can get wrapped token"); - - let outbound_message = - pallet_liquidity_pools_gateway::OutboundMessageQueue::::get( - T::OutboundMessageNonce::one(), - ) - .expect("expected outbound queue message"); - - assert_eq!( - outbound_message.2, - Message::AddCurrency { - currency: currency_index, - evm_address, - }, - ); - - outbound_message - }); - - let expected_event = - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { - sender, - domain, - message, - nonce: T::OutboundMessageNonce::one(), - }; - - env.pass(Blocks::UntilEvent { - event: expected_event.clone().into(), - limit: 3, - }); - - env.check_event(expected_event) - .expect("expected RouterExecutionSuccess event"); - } - - #[test_runtimes([development])] - fn add_currency_should_fail() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::ForeignAsset(42) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Native - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - - // Should fail to add currency_id which is missing a registered - // Location - let currency_id = CurrencyId::ForeignAsset(100); - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - AssetMetadata { - name: BoundedVec::default(), - symbol: BoundedVec::default(), - decimals: 12, - location: None, - existential_deposit: 1_000_000, - additional: CustomMetadata { - transferability: CrossChainTransferability::LiquidityPools, - mintable: false, - permissioned: false, - pool_currency: false, - local_representation: None, - }, - }, - Some(currency_id) - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - - // Add convertable Location to metadata but remove transferability - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add multilocation to metadata for some random EVM chain id for - // which no instance is registered - Some(Some(liquidity_pools_transferable_multilocation::( - u64::MAX, - [1u8; 20], - ))), - Some(CustomMetadata { - // Changed: Disallow liquidityPools transferability - transferability: CrossChainTransferability::Xcm(Default::default()), - ..Default::default() - }), - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - // Switch transferability from XCM to None - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Changed: Disallow cross chain transferability entirely - transferability: CrossChainTransferability::None, - ..Default::default() - }) - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - }); - } - - #[test_runtimes([development])] - fn allow_investment_currency() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let currency_id = AUSD_CURRENCY_ID; - let pool_id = POOL_ID; - let evm_chain_id: u64 = MOONBEAM_EVM_CHAIN_ID; - let evm_address = [1u8; 20]; - - // Create an AUSD pool - create_ausd_pool::(pool_id); - - enable_liquidity_pool_transferability::(currency_id); - - // Enable LiquidityPools transferability - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add location which can be converted to LiquidityPoolsWrappedToken - Some(Some(liquidity_pools_transferable_multilocation::( - evm_chain_id, - evm_address, - ))), - Some(CustomMetadata { - // Changed: Allow liquidity_pools transferability - transferability: CrossChainTransferability::LiquidityPools, - pool_currency: true, - ..Default::default() - }) - )); - - assert_ok!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ) - ); - - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Charlie.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); - }); - } - - #[test_runtimes([development])] - fn allow_investment_currency_should_fail() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let currency_id = CurrencyId::ForeignAsset(42); - let ausd_currency_id = AUSD_CURRENCY_ID; - - // Should fail if pool does not exist - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); - - // Register currency_id with pool_currency set to true - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - AssetMetadata { - name: BoundedVec::default(), - symbol: BoundedVec::default(), - decimals: 12, - location: None, - existential_deposit: 1_000_000, - additional: CustomMetadata { - pool_currency: true, - ..Default::default() - }, - }, - Some(currency_id) - )); - - // Create pool - create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); - - enable_liquidity_pool_transferability::(ausd_currency_id); - - // Should fail if currency is not liquidityPools transferable - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Disallow any cross chain transferability - transferability: CrossChainTransferability::None, - // Changed: Allow to be usable as pool currency - pool_currency: true, - ..Default::default() - }), - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - // Should fail if currency does not have any Location in metadata - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Changed: Allow liquidityPools transferability - transferability: CrossChainTransferability::LiquidityPools, - // Still allow to be pool currency - pool_currency: true, - ..Default::default() - }), - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - - // Should fail if currency does not have LiquidityPoolsWrappedToken location in - // metadata - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add some location which cannot be converted to - // LiquidityPoolsWrappedToken - Some(Some(VersionedLocation::V4(Default::default()))), - // No change for transferability required as it is already allowed for - // LiquidityPools - None, - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - }); - } - - #[test_runtimes([development])] - fn disallow_investment_currency() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let currency_id = AUSD_CURRENCY_ID; - let pool_id = POOL_ID; - let evm_chain_id: u64 = MOONBEAM_EVM_CHAIN_ID; - let evm_address = [1u8; 20]; - - // Create an AUSD pool - create_ausd_pool::(pool_id); - - enable_liquidity_pool_transferability::(currency_id); - - // Enable LiquidityPools transferability - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add location which can be converted to LiquidityPoolsWrappedToken - Some(Some(liquidity_pools_transferable_multilocation::( - evm_chain_id, - evm_address, - ))), - Some(CustomMetadata { - // Changed: Allow liquidity_pools transferability - transferability: CrossChainTransferability::LiquidityPools, - pool_currency: true, - ..Default::default() - }) - )); - - assert_ok!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ) - ); - - assert_noop!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Charlie.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); - }); - } - - #[test_runtimes([development])] - fn disallow_investment_currency_should_fail() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let currency_id = CurrencyId::ForeignAsset(42); - let ausd_currency_id = AUSD_CURRENCY_ID; - - // Should fail if pool does not exist - assert_noop!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); - - // Register currency_id with pool_currency set to true - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - AssetMetadata { - name: BoundedVec::default(), - symbol: BoundedVec::default(), - decimals: 12, - location: None, - existential_deposit: 1_000_000, - additional: CustomMetadata { - pool_currency: true, - ..Default::default() - }, - }, - Some(currency_id) - )); - - // Create pool - create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); - - enable_liquidity_pool_transferability::(ausd_currency_id); - - // Should fail if currency is not liquidityPools transferable - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Disallow any cross chain transferability - transferability: CrossChainTransferability::None, - // Changed: Allow to be usable as pool currency - pool_currency: true, - ..Default::default() - }), - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - // Should fail if currency does not have any Location in metadata - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Changed: Allow liquidityPools transferability - transferability: CrossChainTransferability::LiquidityPools, - // Still allow to be pool currency - pool_currency: true, - ..Default::default() - }), - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - - // Should fail if currency does not have LiquidityPoolsWrappedToken location in - // metadata - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add some location which cannot be converted to - // LiquidityPoolsWrappedToken - Some(Some(VersionedLocation::V4(Default::default()))), - // No change for transferability required as it is already allowed for - // LiquidityPools - None, - )); - assert_noop!( - pallet_liquidity_pools::Pallet::::disallow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - }); - } - - #[test_runtimes([development])] - fn schedule_upgrade() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - // Only Root can call `schedule_upgrade` - assert_noop!( - pallet_liquidity_pools::Pallet::::schedule_upgrade( - RawOrigin::Signed(Keyring::Bob.into()).into(), - MOONBEAM_EVM_CHAIN_ID, - [7; 20] - ), - BadOrigin - ); - - // Now it finally works - assert_ok!(pallet_liquidity_pools::Pallet::::schedule_upgrade( - ::RuntimeOrigin::root(), - MOONBEAM_EVM_CHAIN_ID, - [7; 20] - )); - }); - } - - #[test_runtimes([development])] - fn cancel_upgrade() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - // Only Root can call `cancel_upgrade` - assert_noop!( - pallet_liquidity_pools::Pallet::::cancel_upgrade( - RawOrigin::Signed(Keyring::Bob.into()).into(), - MOONBEAM_EVM_CHAIN_ID, - [7; 20] - ), - BadOrigin - ); - - // Now it finally works - assert_ok!(pallet_liquidity_pools::Pallet::::cancel_upgrade( - ::RuntimeOrigin::root(), - MOONBEAM_EVM_CHAIN_ID, - [7; 20] - )); - }); - } - - #[test_runtimes([development])] - fn update_tranche_token_metadata() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - // NOTE: Default pool admin is BOB - create_ausd_pool::(pool_id); - - // Missing tranche token should throw - let nonexistent_tranche = [71u8; 16]; - - assert_noop!( - pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - nonexistent_tranche, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::TrancheNotFound - ); - - let tranche_id = default_tranche_id::(pool_id); - - // Moving the update to another domain can be called by anyone - assert_ok!( - pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( - RawOrigin::Signed(Keyring::Alice.into()).into(), - pool_id, - tranche_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ) - ); - - // Edge case: Should throw if tranche exists but metadata does not exist - let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_id); - - orml_asset_registry::Metadata::::remove(tranche_currency_id); - - assert_noop!( - pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( - RawOrigin::Signed(POOL_ADMIN.into()).into(), - pool_id, - tranche_id, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - ), - pallet_liquidity_pools::Error::::TrancheMetadataNotFound - ); - }); - } - } - - mod foreign_investments { - use super::*; - - mod same_currencies { - use super::*; - - #[test_runtimes([development])] - fn increase_invest_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial investment - do_initial_increase_investment::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - - // Verify the order was updated to the amount - assert_eq!( - pallet_investments::Pallet::::acc_active_invest_order( - default_investment_id::(), - ) - .amount, - amount - ); - - // Increasing again should just bump invest_amount - let msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - }); - } - - #[test_runtimes([development])] - fn decrease_invest_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let invest_amount: u128 = 10 * decimals(12); - let decrease_amount = invest_amount / 3; - let final_amount = invest_amount - decrease_amount; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id: CurrencyId = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial investment - do_initial_increase_investment::( - pool_id, - invest_amount, - investor.clone(), - currency_id, - ); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: decrease_amount, - }; - - // Expect failure if transferability is disabled since this is required for - // preparing the `ExecutedDecreaseInvest` message. - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg.clone() - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - enable_liquidity_pool_transferability::(currency_id); - - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Verify investment was decreased into investment account - assert_eq!( - orml_tokens::Pallet::::balance( - currency_id, - &default_investment_account::() - ), - final_amount - ); - // Since the investment was done in the pool currency, the decrement happens - // synchronously and thus it must be burned from investor's holdings - assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == pallet_investments::Event::::InvestOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: final_amount - } - .into())); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == orml_tokens::Event::::Withdrawn { - currency_id, - who: investor.clone(), - amount: decrease_amount - } - .into())); - assert_eq!( - pallet_investments::Pallet::::acc_active_invest_order( - default_investment_id::(), - ) - .amount, - final_amount - ); - }); - } - - #[test_runtimes([development])] - fn cancel_invest_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let invest_amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial investment - do_initial_increase_investment::( - pool_id, - invest_amount, - investor.clone(), - currency_id, - ); - - // Verify investment account holds funds before cancelling - assert_eq!( - orml_tokens::Pallet::::balance( - currency_id, - &default_investment_account::() - ), - invest_amount - ); - - // Mock incoming cancel message - let msg = LiquidityPoolMessage::CancelInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - }; - - // Expect failure if transferability is disabled since this is required for - // preparing the `ExecutedDecreaseInvest` message. - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg.clone() - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - enable_liquidity_pool_transferability::(currency_id); - - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Verify investment was entirely drained from investment account - assert_eq!( - orml_tokens::Pallet::::balance( - currency_id, - &default_investment_account::() - ), - 0 - ); - // Since the investment was done in the pool currency, the decrement happens - // synchronously and thus it must be burned from investor's holdings - assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == pallet_investments::Event::::InvestOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: 0 - } - .into())); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == orml_tokens::Event::::Withdrawn { - currency_id, - who: investor.clone(), - amount: invest_amount - } - .into())); - assert_eq!( - pallet_investments::Pallet::::acc_active_invest_order( - default_investment_id::(), - ) - .amount, - 0 - ); - }); - } - - #[test_runtimes([development])] - fn collect_invest_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let sending_domain_locator = - Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - enable_liquidity_pool_transferability::(currency_id); - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - let investment_currency_id: CurrencyId = default_investment_id::().into(); - // Set permissions and execute initial investment - do_initial_increase_investment::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - let events_before_collect = frame_system::Pallet::::events(); - - // Process and fulfill order - // NOTE: Without this step, the order id is not cleared and - // `Event::InvestCollectedForNonClearedOrderId` be dispatched - assert_ok!(pallet_investments::Pallet::::process_invest_orders( - default_investment_id::() - )); - - // Tranche tokens will be minted upon fulfillment - assert_eq!( - orml_tokens::Pallet::::total_issuance(investment_currency_id), - 0 - ); - assert_ok!(pallet_investments::Pallet::::invest_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::one(), - } - )); - assert_eq!( - orml_tokens::Pallet::::total_issuance(investment_currency_id), - amount - ); - - // Mock collection message msg - let msg = LiquidityPoolMessage::CollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Remove events before collect execution - let events_since_collect: Vec<_> = frame_system::Pallet::::events() - .into_iter() - .filter(|e| !events_before_collect.contains(e)) - .collect(); - - // Verify investment was transferred to the domain locator - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &sending_domain_locator - ), - amount - ); - - // Order should have been cleared by fulfilling investment - assert_eq!( - pallet_investments::Pallet::::acc_active_invest_order( - default_investment_id::(), - ) - .amount, - 0 - ); - assert!(!events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::InvestCollectedForNonClearedOrderId { - investment_id: default_investment_id::(), - who: investor.clone(), - } - .into() - })); - - // Order should not have been updated since everything is collected - assert!(!events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::InvestOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: 0, - } - .into() - })); - - // Order should have been fully collected - assert!(events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::InvestOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![0], - who: investor.clone(), - collection: InvestCollection:: { - payout_investment_invest: amount, - remaining_investment_invest: 0, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - - let sender = ::Sender::get(); - - // Clearing of foreign InvestState should be dispatched - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: amount, - tranche_tokens_payout: amount, - remaining_invest_amount: 0, - }, - } - .into() - })); - }); - } - - #[test_runtimes([development])] - fn partially_collect_investment_for_through_investments() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let invest_amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let sending_domain_locator = - Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - do_initial_increase_investment::( - pool_id, - invest_amount, - investor.clone(), - currency_id, - ); - enable_liquidity_pool_transferability::(currency_id); - let investment_currency_id: CurrencyId = default_investment_id::().into(); - - assert!( - !pallet_investments::Pallet::::investment_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Process 50% of investment at 25% rate, i.e. 1 pool currency = 4 tranche - // tokens - assert_ok!(pallet_investments::Pallet::::process_invest_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::invest_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::from_percent(50), - price: Ratio::checked_from_rational(1, 4).unwrap(), - } - )); - - // Pre collect assertions - assert!( - pallet_investments::Pallet::::investment_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Collecting through Investments should denote amounts and transition - // state - assert_ok!(pallet_investments::Pallet::::collect_investments_for( - RawOrigin::Signed(Keyring::Alice.into()).into(), - investor.clone(), - default_investment_id::() - )); - assert!( - !pallet_investments::Pallet::::investment_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Tranche Tokens should still be transferred to collected to - // domain locator account already - assert_eq!( - orml_tokens::Pallet::::balance(investment_currency_id, &investor), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::balance( - investment_currency_id, - &sending_domain_locator - ), - invest_amount * 2 - ); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::InvestOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![0], - who: investor.clone(), - collection: InvestCollection:: { - payout_investment_invest: invest_amount * 2, - remaining_investment_invest: invest_amount / 2, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - - let sender = ::Sender::get(); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: pallet_liquidity_pools::Message::ExecutedCollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: invest_amount / 2, - tranche_tokens_payout: invest_amount * 2, - remaining_invest_amount: invest_amount / 2, - }, - } - .into() - })); - - // Process rest of investment at 50% rate (1 pool currency = 2 tranche tokens) - assert_ok!(pallet_investments::Pallet::::process_invest_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::invest_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::checked_from_rational(1, 2).unwrap(), - } - )); - // Order should have been cleared by fulfilling investment - assert_eq!( - pallet_investments::Pallet::::acc_active_invest_order( - default_investment_id::(), - ) - .amount, - 0 - ); - assert_eq!( - orml_tokens::Pallet::::total_issuance(investment_currency_id), - invest_amount * 3 - ); - - // Collect remainder through Investments - assert_ok!(pallet_investments::Pallet::::collect_investments_for( - RawOrigin::Signed(Keyring::Alice.into()).into(), - investor.clone(), - default_investment_id::() - )); - assert!( - !pallet_investments::Pallet::::investment_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Tranche Tokens should be transferred to collected to - // domain locator account already - let amount_tranche_tokens = invest_amount * 3; - assert_eq!( - orml_tokens::Pallet::::total_issuance(investment_currency_id), - amount_tranche_tokens - ); - assert!( - orml_tokens::Pallet::::balance(investment_currency_id, &investor) - .is_zero() - ); - assert_eq!( - orml_tokens::Pallet::::balance( - investment_currency_id, - &sending_domain_locator - ), - amount_tranche_tokens - ); - assert!(!frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::InvestCollectedForNonClearedOrderId { - investment_id: default_investment_id::(), - who: investor.clone(), - } - .into() - })); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::InvestOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![1], - who: investor.clone(), - collection: InvestCollection:: { - payout_investment_invest: invest_amount, - remaining_investment_invest: 0, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: invest_amount / 2, - tranche_tokens_payout: invest_amount, - remaining_invest_amount: 0, - }, - } - .into() - })); - - // Should fail to collect if `InvestmentState` does not - // exist - let msg = LiquidityPoolMessage::CollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - ), - pallet_foreign_investments::Error::::InfoNotFound - ); - }); - } - - #[test_runtimes([development])] - fn increase_redeem_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial redemption - do_initial_increase_redemption::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - - // Verify amount was noted in the corresponding order - assert_eq!( - pallet_investments::Pallet::::acc_active_redeem_order( - default_investment_id::(), - ) - .amount, - amount - ); - - // Increasing again should just bump redeeming amount - assert_ok!(orml_tokens::Pallet::::mint_into( - default_investment_id::().into(), - &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), - amount - )); - let msg = LiquidityPoolMessage::IncreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - }); - } - - #[test_runtimes([development])] - fn decrease_redeem_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let redeem_amount = 10 * decimals(12); - let decrease_amount = redeem_amount / 3; - let final_amount = redeem_amount - decrease_amount; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let sending_domain_locator = - Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial redemption - do_initial_increase_redemption::( - pool_id, - redeem_amount, - investor.clone(), - currency_id, - ); - - // Verify the corresponding redemption order id is 0 - assert_eq!( - pallet_investments::Pallet::::invest_order_id(investment_id::( - pool_id, - default_tranche_id::(pool_id) - )), - 0 - ); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::DecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: decrease_amount, - }; - - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Verify investment was decreased into investment account - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &default_investment_account::(), - ), - final_amount - ); - // Tokens should have been transferred from investor's wallet to domain's - // sovereign account - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &investor - ), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &sending_domain_locator - ), - decrease_amount - ); - - // Order should have been updated - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == pallet_investments::Event::::RedeemOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: final_amount - } - .into())); - assert_eq!( - pallet_investments::Pallet::::acc_active_redeem_order( - default_investment_id::(), - ) - .amount, - final_amount - ); - - let sender = ::Sender::get(); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedDecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - tranche_tokens_payout: decrease_amount, - remaining_redeem_amount: final_amount, - }, - } - .into() - })); - }); - } - - #[test_runtimes([development])] - fn cancel_redeem_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let redeem_amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let sending_domain_locator = - Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial redemption - do_initial_increase_redemption::( - pool_id, - redeem_amount, - investor.clone(), - currency_id, - ); - - // Verify the corresponding redemption order id is 0 - assert_eq!( - pallet_investments::Pallet::::invest_order_id(investment_id::( - pool_id, - default_tranche_id::(pool_id) - )), - 0 - ); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::CancelRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - }; - - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Verify investment was decreased into investment account - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &default_investment_account::(), - ), - 0 - ); - // Tokens should have been transferred from investor's wallet to domain's - // sovereign account - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &investor - ), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &sending_domain_locator - ), - redeem_amount - ); - - // Order should have been updated - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == pallet_investments::Event::::RedeemOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: 0 - } - .into())); - assert_eq!( - pallet_investments::Pallet::::acc_active_redeem_order( - default_investment_id::(), - ) - .amount, - 0 - ); - }); - } - - #[test_runtimes([development])] - fn fully_collect_redeem_order() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } - .into_account_truncating(); - - // Create new pool - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - - // Set permissions and execute initial investment - do_initial_increase_redemption::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - let events_before_collect = frame_system::Pallet::::events(); - - // Fund the pool account with sufficient pool currency, else redemption cannot - // swap tranche tokens against pool currency - assert_ok!(orml_tokens::Pallet::::mint_into( - currency_id, - &pool_account, - amount - )); - - // Process and fulfill order - // NOTE: Without this step, the order id is not cleared and - // `Event::RedeemCollectedForNonClearedOrderId` be dispatched - assert_ok!(pallet_investments::Pallet::::process_redeem_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::one(), - } - )); - - // Enable liquidity pool transferability - enable_liquidity_pool_transferability::(currency_id); - - // Mock collection message msg - let msg = LiquidityPoolMessage::CollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - }; - - // Execute byte message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Remove events before collect execution - let events_since_collect: Vec<_> = frame_system::Pallet::::events() - .into_iter() - .filter(|e| !events_before_collect.contains(e)) - .collect(); - - // Verify collected redemption was burned from investor - assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == orml_tokens::Event::::Withdrawn { - currency_id, - who: investor.clone(), - amount - } - .into())); - - // Order should have been cleared by fulfilling redemption - assert_eq!( - pallet_investments::Pallet::::acc_active_redeem_order( - default_investment_id::(), - ) - .amount, - 0 - ); - assert!(!events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemCollectedForNonClearedOrderId { - investment_id: default_investment_id::(), - who: investor.clone(), - } - .into() - })); - - // Order should not have been updated since everything is collected - assert!(!events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemOrderUpdated { - investment_id: default_investment_id::(), - submitted_at: 0, - who: investor.clone(), - amount: 0, - } - .into() - })); - - // Order should have been fully collected - assert!(events_since_collect.iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![0], - who: investor.clone(), - collection: RedeemCollection:: { - payout_investment_redeem: amount, - remaining_investment_redeem: 0, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - - let sender = ::Sender::get(); - - // Clearing of foreign RedeemState should be dispatched - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: amount, - tranche_tokens_payout: amount, - remaining_redeem_amount: 0, - }, - } - .into() - })); - }); - } - - #[test_runtimes([development])] - fn partially_collect_redemption_for_through_investments() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let redeem_amount = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } - .into_account_truncating(); - create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - do_initial_increase_redemption::( - pool_id, - redeem_amount, - investor.clone(), - currency_id, - ); - enable_liquidity_pool_transferability::(currency_id); - - // Fund the pool account with sufficient pool currency, else redemption cannot - // swap tranche tokens against pool currency - assert_ok!(orml_tokens::Pallet::::mint_into( - currency_id, - &pool_account, - redeem_amount - )); - assert!( - !pallet_investments::Pallet::::redemption_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Process 50% of redemption at 25% rate, i.e. 1 pool currency = 4 tranche - // tokens - assert_ok!(pallet_investments::Pallet::::process_redeem_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::from_percent(50), - price: Ratio::checked_from_rational(1, 4).unwrap(), - } - )); - - // Pre collect assertions - assert!( - pallet_investments::Pallet::::redemption_requires_collect( - &investor, - default_investment_id::() - ) - ); - - // Collecting through investments should denote amounts and transition - // state - assert_ok!(pallet_investments::Pallet::::collect_redemptions_for( - RawOrigin::Signed(Keyring::Alice.into()).into(), - investor.clone(), - default_investment_id::() - )); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![0], - who: investor.clone(), - collection: RedeemCollection:: { - payout_investment_redeem: redeem_amount / 8, - remaining_investment_redeem: redeem_amount / 2, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - - let sender = ::Sender::get(); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: redeem_amount / 8, - tranche_tokens_payout: redeem_amount / 2, - remaining_redeem_amount: redeem_amount / 2, - }, - } - .into() - })); - assert!( - !pallet_investments::Pallet::::redemption_requires_collect( - &investor, - default_investment_id::() - ) - ); - // Since foreign currency is pool currency, the swap is immediately fulfilled - // and ExecutedCollectRedeem dispatched - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == orml_tokens::Event::::Withdrawn { - currency_id, - who: investor.clone(), - amount: redeem_amount / 8 - } - .into())); - - // Process rest of redemption at 50% rate - assert_ok!(pallet_investments::Pallet::::process_redeem_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::checked_from_rational(1, 2).unwrap(), - } - )); - // Order should have been cleared by fulfilling redemption - assert_eq!( - pallet_investments::Pallet::::acc_active_redeem_order( - default_investment_id::(), - ) - .amount, - 0 - ); - - // Collect remainder through Investments - assert_ok!(pallet_investments::Pallet::::collect_redemptions_for( - RawOrigin::Signed(Keyring::Alice.into()).into(), - investor.clone(), - default_investment_id::() - )); - assert!( - !pallet_investments::Pallet::::redemption_requires_collect( - &investor, - default_investment_id::() - ) - ); - assert!(!frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemCollectedForNonClearedOrderId { - investment_id: default_investment_id::(), - who: investor.clone(), - } - .into() - })); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_investments::Event::::RedeemOrdersCollected { - investment_id: default_investment_id::(), - processed_orders: vec![1], - who: investor.clone(), - collection: RedeemCollection:: { - payout_investment_redeem: redeem_amount / 4, - remaining_investment_redeem: 0, - }, - outcome: CollectOutcome::FullyCollected, - } - .into() - })); - // Verify collected redemption was burned from investor - assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); - assert!(frame_system::Pallet::::events().iter().any(|e| e.event - == orml_tokens::Event::::Withdrawn { - currency_id, - who: investor.clone(), - amount: redeem_amount / 4 - } - .into())); - // Clearing of foreign RedeemState should have been dispatched exactly once - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - currency_payout: redeem_amount / 4, - tranche_tokens_payout: redeem_amount / 2, - remaining_redeem_amount: 0, - }, - } - .into() - })); - }); - } - - mod should_fail { - use super::*; - - mod decrease_should_underflow { - use super::*; - - #[test_runtimes([development])] - fn invest_decrease_underflow() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let invest_amount: u128 = 10 * decimals(12); - let decrease_amount = invest_amount + 1; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id: CurrencyId = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - create_currency_pool::( - pool_id, - currency_id, - currency_decimals.into(), - ); - do_initial_increase_investment::( - pool_id, - invest_amount, - investor.clone(), - currency_id, - ); - enable_liquidity_pool_transferability::(currency_id); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: decrease_amount, - }; - - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - ), - pallet_foreign_investments::Error::::TooMuchDecrease - ); - }); - } - - #[test_runtimes([development])] - fn redeem_decrease_underflow() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let redeem_amount: u128 = 10 * decimals(12); - let decrease_amount = redeem_amount + 1; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id: CurrencyId = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - create_currency_pool::( - pool_id, - currency_id, - currency_decimals.into(), - ); - do_initial_increase_redemption::( - pool_id, - redeem_amount, - investor.clone(), - currency_id, - ); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::DecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: decrease_amount, - }; - - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - ), - DispatchError::Arithmetic(sp_runtime::ArithmeticError::Underflow) - ); - }); - } - } - - mod should_throw_requires_collect { - use super::*; - - #[test_runtimes([development])] - fn invest_requires_collect() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount: u128 = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id: CurrencyId = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - create_currency_pool::( - pool_id, - currency_id, - currency_decimals.into(), - ); - do_initial_increase_investment::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - enable_liquidity_pool_transferability::(currency_id); - - // Prepare collection - let pool_account = - pallet_pool_system::pool_types::PoolLocator { pool_id } - .into_account_truncating(); - assert_ok!(orml_tokens::Pallet::::mint_into( - currency_id, - &pool_account, - amount - )); - assert_ok!(pallet_investments::Pallet::::process_invest_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::invest_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::one(), - } - )); - - // Should fail to increase - let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: AUSD_ED, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - ), - pallet_investments::Error::::CollectRequired - ); - - // Should fail to decrease - let decrease_msg = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg - ), - pallet_investments::Error::::CollectRequired - ); - }); - } - - #[test_runtimes([development])] - fn redeem_requires_collect() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let amount: u128 = 10 * decimals(12); - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let currency_id: CurrencyId = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - create_currency_pool::( - pool_id, - currency_id, - currency_decimals.into(), - ); - do_initial_increase_redemption::( - pool_id, - amount, - investor.clone(), - currency_id, - ); - enable_liquidity_pool_transferability::(currency_id); - - // Mint more into DomainLocator required for subsequent invest attempt - assert_ok!(orml_tokens::Pallet::::mint_into( - default_investment_id::().into(), - &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), - 1, - )); - - // Prepare collection - let pool_account = - pallet_pool_system::pool_types::PoolLocator { pool_id } - .into_account_truncating(); - assert_ok!(orml_tokens::Pallet::::mint_into( - currency_id, - &pool_account, - amount - )); - assert_ok!(pallet_investments::Pallet::::process_redeem_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::one(), - } - )); - - // Should fail to increase - let increase_msg = LiquidityPoolMessage::IncreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - ), - pallet_investments::Error::::CollectRequired - ); - - // Should fail to decrease - let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(currency_id), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg - ), - pallet_investments::Error::::CollectRequired - ); - }); - } - } - - mod payment_payout_currency { - use super::*; - - #[test_runtimes([development])] - fn invalid_invest_payment_currency() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * decimals(18); - - create_currency_pool::( - pool_id, - pool_currency, - currency_decimals.into(), - ); - do_initial_increase_investment::( - pool_id, - amount, - investor.clone(), - pool_currency, - ); - - enable_usdt_trading::(pool_currency, amount, true, true, true); - - // Should fail to increase, decrease or collect for - // another foreign currency as long as - // `InvestmentState` exists - let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: AUSD_ED, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - let decrease_msg = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - let collect_msg = LiquidityPoolMessage::CollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - collect_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - }); - } - - #[test_runtimes([development])] - fn invalid_redeem_payout_currency() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * decimals(18); - - create_currency_pool::( - pool_id, - pool_currency, - currency_decimals.into(), - ); - do_initial_increase_redemption::( - pool_id, - amount, - investor.clone(), - pool_currency, - ); - enable_usdt_trading::(pool_currency, amount, true, true, true); - assert_ok!(orml_tokens::Pallet::::mint_into( - default_investment_id::().into(), - &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), - amount, - )); - - // Should fail to increase, decrease or collect for - // another foreign currency as long as - // `RedemptionState` exists - let increase_msg = LiquidityPoolMessage::IncreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - let collect_msg = LiquidityPoolMessage::CollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - collect_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - }); - } - - #[test_runtimes([development])] - fn redeem_payout_currency_not_found() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency = AUSD_CURRENCY_ID; - let currency_decimals = currency_decimals::AUSD; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let amount = 6 * decimals(18); - - create_currency_pool::( - pool_id, - pool_currency, - currency_decimals.into(), - ); - do_initial_increase_redemption::( - pool_id, - amount, - investor.clone(), - pool_currency, - ); - enable_usdt_trading::(pool_currency, amount, true, true, true); - assert_ok!(orml_tokens::Pallet::::mint_into( - default_investment_id::().into(), - &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), - amount, - )); - - // Should fail to decrease or collect for another - // foreign currency as long as `RedemptionState` - // exists - let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: 1, - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - - let collect_msg = LiquidityPoolMessage::CollectRedeem { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - }; - assert_noop!( - pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - collect_msg - ), - pallet_foreign_investments::Error::::MismatchedForeignCurrency - ); - }); - } - } - } - } - - mod mismatching_currencies { - use super::*; - - #[test_runtimes([development])] - fn collect_foreign_investment_for() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency: CurrencyId = AUSD_CURRENCY_ID; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 6 * decimals(18); - let sending_domain_locator = - Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - let trader: AccountId = Keyring::Alice.into(); - create_currency_pool::( - pool_id, - pool_currency, - pool_currency_decimals.into(), - ); - - // USDT investment preparations - let invest_amount_foreign_denominated = enable_usdt_trading::( - pool_currency, - invest_amount_pool_denominated, - true, - true, - // not needed because we don't initialize a swap from pool to foreign here - false, - ); - - // Do first investment and fulfill swap order - do_initial_increase_investment::( - pool_id, - invest_amount_foreign_denominated, - investor.clone(), - foreign_currency, - ); - fulfill_swap_into_pool::( - pool_id, - default_order_id::(&investor), - invest_amount_pool_denominated, - invest_amount_foreign_denominated, - trader, - ); - - // Increase invest order to initialize ForeignInvestmentInfo - let msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg - )); - - // Process 100% of investment at 50% rate (1 pool currency = 2 tranche tokens) - assert_ok!(pallet_investments::Pallet::::process_invest_orders( - default_investment_id::() - )); - assert_ok!(pallet_investments::Pallet::::invest_fulfillment( - default_investment_id::(), - FulfillmentWithPrice { - of_amount: Perquintill::one(), - price: Ratio::checked_from_rational(1, 2).unwrap(), - } - )); - assert_ok!(pallet_investments::Pallet::::collect_investments_for( - RawOrigin::Signed(Keyring::Alice.into()).into(), - investor.clone(), - default_investment_id::() - )); - assert!(orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &investor - ) - .is_zero()); - assert_eq!( - orml_tokens::Pallet::::balance( - default_investment_id::().into(), - &sending_domain_locator - ), - invest_amount_pool_denominated * 2 - ); - - let sender = ::Sender::get(); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedCollectInvest { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - currency_payout: invest_amount_foreign_denominated, - tranche_tokens_payout: 2 * invest_amount_pool_denominated, - remaining_invest_amount: invest_amount_foreign_denominated, - }, - } - .into() - })); - }); - } - - /// Invest in pool currency, then increase in allowed foreign - /// currency, then decrease in same foreign currency multiple times. - #[test_runtimes([development])] - fn increase_fulfill_increase_decrease_decrease_partial() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency: CurrencyId = AUSD_CURRENCY_ID; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 6 * decimals(18); - let trader: AccountId = Keyring::Alice.into(); - create_currency_pool::( - pool_id, - pool_currency, - pool_currency_decimals.into(), - ); - - // USDT investment preparations - let invest_amount_foreign_denominated = enable_usdt_trading::( - pool_currency, - invest_amount_pool_denominated, - true, - true, - true, - ); - - // Do first investment and fulfill swap order - do_initial_increase_investment::( - pool_id, - invest_amount_foreign_denominated, - investor.clone(), - foreign_currency, - ); - fulfill_swap_into_pool::( - pool_id, - default_order_id::(&investor), - invest_amount_pool_denominated, - invest_amount_foreign_denominated, - trader.clone(), - ); - - // Do second investment and not fulfill swap order - let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - )); - - // Decrease pending pool swap by same amount - let decrease_msg_pool_swap_amount = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg_pool_swap_amount - )); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: ::Sender::get(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - currency_payout: invest_amount_foreign_denominated, - remaining_invest_amount: invest_amount_foreign_denominated, - }, - } - .into() - })); - - // Decrease partially investing amount - let decrease_msg_partial_invest_amount = - LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated / 2, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg_partial_invest_amount.clone() - )); - - // Consume entire investing amount by sending same message - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg_partial_invest_amount.clone() - )); - - // Swap decreased amount - assert_ok!(pallet_order_book::Pallet::::fill_order( - RawOrigin::Signed(trader.clone()).into(), - default_order_id::(&investor), - invest_amount_pool_denominated - )); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: ::Sender::get(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - currency_payout: invest_amount_foreign_denominated, - remaining_invest_amount: 0, - }, - } - .into() - })); - }); - } - - /// Propagate swaps only via OrderBook fulfillments. - /// - /// Flow: Increase, fulfill, decrease, fulfill - #[test_runtimes([development])] - fn invest_swaps_happy_path() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![ - (AUSD_CURRENCY_ID, AUSD_ED), - (USDT_CURRENCY_ID, USDT_ED), - ])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let trader: AccountId = Keyring::Alice.into(); - let pool_currency: CurrencyId = AUSD_CURRENCY_ID; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * decimals(18); - create_currency_pool::( - pool_id, - pool_currency, - pool_currency_decimals.into(), - ); - let invest_amount_foreign_denominated: u128 = enable_usdt_trading::( - pool_currency, - invest_amount_pool_denominated, - true, - true, - true, - ); - - // Increase such that active swap into USDT is initialized - do_initial_increase_investment::( - pool_id, - invest_amount_foreign_denominated, - investor.clone(), - foreign_currency, - ); - - // Fulfilling order should propagate it from swapping to investing - let swap_order_id = default_order_id::(&investor); - fulfill_swap_into_pool::( - pool_id, - swap_order_id, - invest_amount_pool_denominated, - invest_amount_foreign_denominated, - trader.clone(), - ); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_order_book::Event::::OrderFulfillment { - order_id: swap_order_id, - placing_account: investor.clone(), - fulfilling_account: trader.clone(), - partial_fulfillment: false, - fulfillment_amount: invest_amount_foreign_denominated, - currency_in: pool_currency, - currency_out: foreign_currency, - ratio: Ratio::one(), - } - .into() - })); - - // Decrease by half the investment amount - let msg = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated / 2, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - msg.clone() - )); - - let swap_order_id = default_order_id::(&investor); - assert_ok!(pallet_order_book::Pallet::::fill_order( - RawOrigin::Signed(trader.clone()).into(), - swap_order_id, - invest_amount_pool_denominated / 2 - )); - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_order_book::Event::::OrderFulfillment { - order_id: swap_order_id, - placing_account: investor.clone(), - fulfilling_account: trader.clone(), - partial_fulfillment: false, - fulfillment_amount: invest_amount_pool_denominated / 2, - currency_in: foreign_currency, - currency_out: pool_currency, - ratio: Ratio::one(), - } - .into() - })); - - let sender = ::Sender::get(); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: sender.clone(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - currency_payout: invest_amount_foreign_denominated / 2, - remaining_invest_amount: invest_amount_foreign_denominated / 2, - }, - } - .into() - })); - }); - } - - #[test_runtimes([development])] - fn increase_fulfill_decrease_fulfill_partial_increase() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let pool_id = POOL_ID; - let investor: AccountId = - AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); - let pool_currency: CurrencyId = AUSD_CURRENCY_ID; - let foreign_currency: CurrencyId = USDT_CURRENCY_ID; - let pool_currency_decimals = currency_decimals::AUSD; - let invest_amount_pool_denominated: u128 = 10 * decimals(18); - let trader: AccountId = Keyring::Alice.into(); - create_currency_pool::( - pool_id, - pool_currency, - pool_currency_decimals.into(), - ); - - // USDT investment preparations - let invest_amount_foreign_denominated = enable_usdt_trading::( - pool_currency, - invest_amount_pool_denominated, - true, - true, - true, - ); - - // Do first investment and fulfill swap order - do_initial_increase_investment::( - pool_id, - invest_amount_foreign_denominated, - investor.clone(), - foreign_currency, - ); - fulfill_swap_into_pool::( - pool_id, - default_order_id::(&investor), - invest_amount_pool_denominated, - invest_amount_foreign_denominated, - trader.clone(), - ); - - // Decrease pending pool swap by same amount - let decrease_msg_pool_swap_amount = LiquidityPoolMessage::DecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - decrease_msg_pool_swap_amount - )); - - // Fulfill decrease swap partially - assert_ok!(pallet_order_book::Pallet::::fill_order( - RawOrigin::Signed(trader.clone()).into(), - default_order_id::(&investor), - 3 * invest_amount_pool_denominated / 4 - )); - - // Increase more than pending swap (pool -> foreign) amount from decrease - let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - amount: invest_amount_foreign_denominated / 2, - }; - assert_ok!(pallet_liquidity_pools::Pallet::::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - increase_msg - )); - - assert!(frame_system::Pallet::::events().iter().any(|e| { - e.event - == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { - sender: ::Sender::get(), - domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), - message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { - pool_id, - tranche_id: default_tranche_id::(pool_id), - investor: investor.clone().into(), - currency: general_currency_index::(foreign_currency), - currency_payout: invest_amount_foreign_denominated, - remaining_invest_amount: invest_amount_foreign_denominated / 2, - }, - } - .into() - })); - }); - } - } - } - - mod transfers { - use super::*; - - #[test_runtimes([development])] - fn transfer_non_tranche_tokens_from_local() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let initial_balance = 2 * AUSD_ED; - let amount = initial_balance / 2; - let dest_address = DEFAULT_DOMAIN_ADDRESS_MOONBEAM; - let currency_id = AUSD_CURRENCY_ID; - let source_account = Keyring::Charlie; - - // Mint sufficient balance - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), - 0 - ); - assert_ok!(orml_tokens::Pallet::::mint_into( - currency_id, - &source_account.into(), - initial_balance - )); - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), - initial_balance - ); - - // Only `ForeignAsset` can be transferred - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - CurrencyId::Tranche(42u64, [0u8; 16]), - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::InvalidTransferCurrency - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards), - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - CurrencyId::Native, - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - - // Cannot transfer as long as cross chain transferability is disabled - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - currency_id, - dest_address.clone(), - initial_balance, - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - // Enable LiquidityPools transferability - enable_liquidity_pool_transferability::(currency_id); - - // Cannot transfer more than owned - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - currency_id, - dest_address.clone(), - initial_balance.saturating_add(1), - ), - pallet_liquidity_pools::Error::::BalanceTooLow - ); - - let pre_total_issuance = orml_tokens::Pallet::::total_issuance(currency_id); - - assert_ok!(pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(source_account.into()).into(), - currency_id, - dest_address.clone(), - amount, - )); - - assert_eq!( - orml_tokens::Pallet::::total_issuance(currency_id), - pre_total_issuance - amount - ); - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), - initial_balance - amount - ); - }); - } - - fn transfer_cfg_to_sibling(env: &mut FudgeEnv) { - let alice_initial_balance = cfg(1_000); - let transfer_amount = cfg(5); - let cfg_in_sibling = CurrencyId::ForeignAsset(12); - - // CFG Metadata - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - - env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - 0 - ); - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(CurrencyId::Native), - )); - }); - - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - 0 - ); - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(cfg_in_sibling) - )); - }); - - env.parachain_state_mut(|| { - assert_ok!(pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - CurrencyId::Native, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - }, - ], - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); - - // Confirm that Keyring::Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - transfer_amount - ); - - // Verify that the amount transferred is now part of the sibling account here - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - transfer_amount - ); - }); - - env.pass(Blocks::ByNumber(2)); - - env.sibling_state(|| { - let current_balance = - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()); - - // Verify that Keyring::Bob now has (amount transferred - fee) - assert_eq!(current_balance, transfer_amount - fee(18)); - - // Sanity check for the actual amount Keyring::Bob ends up with - assert_eq!(current_balance, 4993570400000000000); - }); - } - - #[test_runtimes([development])] - fn transfer_cfg_to_and_from_sibling() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - // In order to be able to transfer CFG from Moonbeam to Development, we need to - // first send CFG from Development to Moonbeam, or else it fails since it'd be - // like Moonbeam had minted CFG on their side. - transfer_cfg_to_sibling::(&mut env); - - let para_to_sibling_transfer_amount = cfg(5); - - let alice_balance = cfg(1_000) - para_to_sibling_transfer_amount; - let bob_balance = para_to_sibling_transfer_amount - fee(18); - let charlie_balance = cfg(1_000); - - let sibling_to_para_transfer_amount = cfg(4); - // Note: This asset was registered in `transfer_cfg_to_sibling` - let cfg_in_sibling = CurrencyId::ForeignAsset(12); - - env.parachain_state(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_balance - ); - }); - - env.sibling_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::PARA_ID - )), - 0 - ); - - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - bob_balance - ); - }); - - env.sibling_state_mut(|| { - assert_ok!(pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Bob.into()).into(), - cfg_in_sibling, - sibling_to_para_transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Charlie.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); - - // Confirm that Charlie's balance is initial balance - amount transferred - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - bob_balance - sibling_to_para_transfer_amount - ); - }); - - env.pass(Blocks::ByNumber(3)); - - env.parachain_state(|| { - // Verify that Charlie's balance equals the amount transferred - fee - assert_eq!( - pallet_balances::Pallet::::free_balance(&Into::::into( - Keyring::Charlie - )), - charlie_balance + sibling_to_para_transfer_amount - cfg_fee(), - ); - }); - } - } - - mod routers { - use super::*; - - mod axelar_evm { - use std::ops::AddAssign; - - use super::*; - - #[test_runtimes([development])] - fn test_via_outbound_queue() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::council_members::(get_council_members())) - .storage(), - ); - - let test_domain = Domain::EVM(1); - - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code: Vec = vec![0, 0, 0]; - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let liquidity_pools_contract_address = H160::from_low_u64_be(2); - - env.parachain_state_mut(|| { - pallet_evm::AccountCodes::::insert( - axelar_contract_address, - axelar_contract_code, - ) - }); - - let transaction_call_cost = env - .parachain_state(|| ::config().gas_transaction_call); - - let evm_domain = EVMDomain { - target_contract_address: axelar_contract_address, - target_contract_hash: axelar_contract_hash, - fee_values: FeeValues { - value: U256::from(0), - gas_limit: U256::from(transaction_call_cost + 1_000_000), - gas_price: U256::from(10), - }, - }; - - let axelar_evm_router = AxelarEVMRouter:: { - router: EVMRouter { - evm_domain, - _marker: Default::default(), - }, - evm_chain: BoundedVec::>::try_from( - "ethereum".as_bytes().to_vec(), - ) - .unwrap(), - _marker: Default::default(), - liquidity_pools_contract_address, - }; - - let test_router = DomainRouter::::AxelarEVM(axelar_evm_router); - - env.parachain_state_mut(|| { - assert_ok!( - pallet_liquidity_pools_gateway::Pallet::::set_domain_router( - ::RuntimeOrigin::root(), - test_domain.clone(), - test_router, - ) - ); - }); - - let sender = Keyring::Alice.id(); - let gateway_sender = env.parachain_state(|| { - ::Sender::get() - }); - - let gateway_sender_h160: H160 = H160::from_slice( - &>::as_ref(&gateway_sender) - [0..20], - ); - - let msg = LiquidityPoolMessage::Transfer { - currency: 0, - sender: Keyring::Alice.id().into(), - receiver: Keyring::Bob.id().into(), - amount: 1_000u128, - }; - - // Failure - gateway sender account is not funded. - assert_ok!(env.parachain_state_mut(|| { - as OutboundQueue>::submit( - sender.clone(), - test_domain.clone(), - msg.clone(), - ) - })); - - let mut nonce = T::OutboundMessageNonce::one(); - - let expected_event = - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionFailure { - sender: gateway_sender.clone(), - domain: test_domain.clone(), - message: msg.clone(), - error: pallet_evm::Error::::BalanceLow.into(), - nonce, - }; - - env.pass(Blocks::UntilEvent { - event: expected_event.clone().into(), - limit: 3, - }); - - env.check_event(expected_event) - .expect("expected RouterExecutionFailure event"); - - nonce.add_assign(T::OutboundMessageNonce::one()); - - assert_ok!(env.parachain_state_mut(|| { - // Note how both the target address and the gateway sender need to have some - // balance. - crate::generic::utils::evm::mint_balance_into_derived_account::( - axelar_contract_address, - cfg(1_000_000_000), - ); - crate::generic::utils::evm::mint_balance_into_derived_account::( - gateway_sender_h160, - cfg(1_000_000), - ); - - as OutboundQueue>::submit( - sender.clone(), - test_domain.clone(), - msg.clone(), - ) - })); - - let expected_event = - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { - sender: gateway_sender.clone(), - domain: test_domain.clone(), - message: msg.clone(), - nonce, - }; - - env.pass(Blocks::UntilEvent { - event: expected_event.clone().into(), - limit: 3, - }); - - env.check_event(expected_event) - .expect("expected RouterExecutionSuccess event"); - - // Router not found - let unused_domain = Domain::EVM(1234); - - env.parachain_state_mut(|| { - assert_noop!( - as OutboundQueue>::submit( - sender, - unused_domain.clone(), - msg, - ), - pallet_liquidity_pools_gateway::Error::::RouterNotFound - ); - }); - } - } - - mod ethereum_xcm { - use utils::*; - - use super::*; - - mod utils { - use super::*; - - pub fn submit_test_fn( - router_creation_fn: RouterCreationFn, - ) { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .storage(), - ); - - setup_test(&mut env); - - let msg = Message::::Transfer { - currency: 0, - sender: Keyring::Alice.into(), - receiver: Keyring::Bob.into(), - amount: 1_000u128, - }; - - env.parachain_state_mut(|| { - let domain_router = router_creation_fn( - Location::new(1, Parachain(T::FudgeHandle::SIBLING_ID)).into(), - GLMR_CURRENCY_ID, - ); - - assert_ok!( - pallet_liquidity_pools_gateway::Pallet::::set_domain_router( - ::RuntimeOrigin::root(), - TEST_DOMAIN, - domain_router, - ) - ); - - assert_ok!( - as OutboundQueue>::submit( - Keyring::Alice.into(), - TEST_DOMAIN, - msg.clone(), - ) - ); - }); - - let gateway_sender = env.parachain_state(|| { - ::Sender::get() - }); - - let expected_event = - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { - sender: gateway_sender, - domain: TEST_DOMAIN, - message: msg, - nonce: T::OutboundMessageNonce::one(), - }; - - env.pass(Blocks::UntilEvent { - event: expected_event.clone().into(), - limit: 3, - }); - - env.check_event(expected_event) - .expect("expected RouterExecutionSuccess event"); - } - - type RouterCreationFn = - Box DomainRouter>; - - pub fn get_axelar_xcm_router_fn() -> RouterCreationFn - { - Box::new( - |location: VersionedLocation, currency_id: CurrencyId| -> DomainRouter { - let router = AxelarXCMRouter:: { - router: XCMRouter { - xcm_domain: XcmDomain { - location: Box::new( - location.try_into().expect("Bad xcm domain location"), - ), - ethereum_xcm_transact_call_index: BoundedVec::truncate_from( - vec![38, 0], - ), - contract_address: H160::from_low_u64_be(11), - max_gas_limit: 700_000, - transact_required_weight_at_most: Default::default(), - overall_weight: Default::default(), - fee_currency: currency_id, - fee_amount: decimals(18).saturating_div(5), - }, - _marker: Default::default(), - }, - axelar_target_chain: BoundedVec::< - u8, - ConstU32, - >::try_from( - "ethereum".as_bytes().to_vec() - ) - .unwrap(), - axelar_target_contract: H160::from_low_u64_be(111), - _marker: Default::default(), - }; - - DomainRouter::AxelarXCM(router) - }, - ) - } - - pub fn get_ethereum_xcm_router_fn() -> RouterCreationFn - { - Box::new( - |location: VersionedLocation, currency_id: CurrencyId| -> DomainRouter { - let router = EthereumXCMRouter:: { - router: XCMRouter { - xcm_domain: XcmDomain { - location: Box::new( - location.try_into().expect("Bad xcm domain location"), - ), - ethereum_xcm_transact_call_index: BoundedVec::truncate_from( - vec![38, 0], - ), - contract_address: H160::from_low_u64_be(11), - max_gas_limit: 700_000, - transact_required_weight_at_most: Default::default(), - overall_weight: Default::default(), - fee_currency: currency_id, - fee_amount: decimals(18).saturating_div(5), - }, - _marker: Default::default(), - }, - _marker: Default::default(), - }; - - DomainRouter::EthereumXCM(router) - }, - ) - } - } - - const TEST_DOMAIN: Domain = Domain::EVM(1); - - #[test_runtimes([development])] - fn submit_ethereum_xcm() { - submit_test_fn::(get_ethereum_xcm_router_fn::()); - } - - #[test_runtimes([development])] - fn submit_axelar_xcm() { - submit_test_fn::(get_axelar_xcm_router_fn::()); - } - } - } - - mod gateway { - use super::*; - - #[test_runtimes([development])] - fn set_domain_router() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::council_members::(get_council_members())) - .storage(), + enable_liquidity_pool_transferability::(currency_id); + + assert_eq!( + orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), + DEFAULT_BALANCE_GLMR ); - let test_domain = Domain::EVM(1); + assert_ok!(pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id, + )); - let axelar_contract_address = H160::from_low_u64_be(1); - let axelar_contract_code: Vec = vec![0, 0, 0]; - let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); - let liquidity_pools_contract_address = H160::from_low_u64_be(2); + let currency_index = + pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) + .expect("can get general index for currency"); - env.parachain_state_mut(|| { - pallet_evm::AccountCodes::::insert(axelar_contract_address, axelar_contract_code) - }); + let LiquidityPoolsWrappedToken::EVM { + address: evm_address, + .. + } = pallet_liquidity_pools::Pallet::::try_get_wrapped_token(¤cy_id) + .expect("can get wrapped token"); - let evm_domain = EVMDomain { - target_contract_address: axelar_contract_address, - target_contract_hash: axelar_contract_hash, - fee_values: FeeValues { - value: U256::from(10), - gas_limit: U256::from(1_000_000), - gas_price: U256::from(10), - }, - }; + let outbound_message = pallet_liquidity_pools_gateway::OutboundMessageQueue::::get( + T::OutboundMessageNonce::one(), + ) + .expect("expected outbound queue message"); - let axelar_evm_router = AxelarEVMRouter:: { - router: EVMRouter { - evm_domain, - _marker: Default::default(), + assert_eq!( + outbound_message.2, + Message::AddCurrency { + currency: currency_index, + evm_address, }, - evm_chain: BoundedVec::>::try_from( - "ethereum".as_bytes().to_vec(), - ) - .unwrap(), - _marker: Default::default(), - liquidity_pools_contract_address, - }; - - let test_router = DomainRouter::::AxelarEVM(axelar_evm_router); - - let set_domain_router_call = - set_domain_router_call(test_domain.clone(), test_router.clone()); - - let council_threshold = 2; - let voting_period = 3; - - execute_via_democracy::( - &mut env, - get_council_members(), - set_domain_router_call, - council_threshold, - voting_period, - 0, - 0, ); + }); + } - env.parachain_state(|| { - let router = - pallet_liquidity_pools_gateway::Pallet::::domain_routers(test_domain) - .expect("domain router is set"); + #[test_runtimes([development])] + fn add_currency_should_fail() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - assert!(router.eq(&test_router)); - }); - } + setup_test(&mut env); - #[test_runtimes([development])] - fn add_remove_instances() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::council_members::(get_council_members())) - .storage(), + env.parachain_state_mut(|| { + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + CurrencyId::ForeignAsset(42) + ), + pallet_liquidity_pools::Error::::AssetNotFound + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + CurrencyId::Native + ), + pallet_liquidity_pools::Error::::AssetNotFound + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) + ), + pallet_liquidity_pools::Error::::AssetNotFound + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) + ), + pallet_liquidity_pools::Error::::AssetNotFound ); - let test_instance = DomainAddress::EVM(1, [0; 20]); - - let add_instance_call = add_instance_call::(test_instance.clone()); + // Should fail to add currency_id which is missing a registered + // Location + let currency_id = CurrencyId::ForeignAsset(100); - let council_threshold = 2; - let voting_period = 3; + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + AssetMetadata { + name: BoundedVec::default(), + symbol: BoundedVec::default(), + decimals: 12, + location: None, + existential_deposit: 1_000_000, + additional: CustomMetadata { + transferability: CrossChainTransferability::LiquidityPools, + mintable: false, + permissioned: false, + pool_currency: false, + local_representation: None, + }, + }, + Some(currency_id) + )); - let (prop_index, ref_index) = execute_via_democracy::( - &mut env, - get_council_members(), - add_instance_call, - council_threshold, - voting_period, - 0, - 0, + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken ); - env.parachain_state(|| { - assert!( - pallet_liquidity_pools_gateway::Allowlist::::contains_key( - test_instance.domain(), - test_instance.clone() - ) - ); - }); - - let remove_instance_call = remove_instance_call::(test_instance.clone()); + // Add convertable Location to metadata but remove transferability + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + // Changed: Add multilocation to metadata for some random EVM chain id for + // which no instance is registered + Some(Some(liquidity_pools_transferable_multilocation::( + u64::MAX, + [1u8; 20], + ))), + Some(CustomMetadata { + // Changed: Disallow liquidityPools transferability + transferability: CrossChainTransferability::Xcm(Default::default()), + ..Default::default() + }), + )); - execute_via_democracy::( - &mut env, - get_council_members(), - remove_instance_call, - council_threshold, - voting_period, - prop_index, - ref_index, + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable ); - env.parachain_state(|| { - assert!( - !pallet_liquidity_pools_gateway::Allowlist::::contains_key( - test_instance.domain(), - test_instance.clone() - ) - ); - }); - } + // Switch transferability from XCM to None + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + None, + Some(CustomMetadata { + // Changed: Disallow cross chain transferability entirely + transferability: CrossChainTransferability::None, + ..Default::default() + }) + )); - #[test_runtimes([development])] - fn process_msg() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::council_members::(get_council_members())) - .storage(), + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable ); + }); + } - let test_instance = DomainAddress::EVM(1, [0; 20]); - - let add_instance_call = add_instance_call::(test_instance.clone()); + #[test_runtimes([development])] + fn allow_investment_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - let council_threshold = 2; - let voting_period = 3; + setup_test(&mut env); - execute_via_democracy::( - &mut env, - get_council_members(), - add_instance_call, - council_threshold, - voting_period, - 0, - 0, - ); + env.parachain_state_mut(|| { + let currency_id = AUSD_CURRENCY_ID; + let pool_id = POOL_ID; + let evm_chain_id: u64 = MOONBEAM_EVM_CHAIN_ID; + let evm_address = [1u8; 20]; - env.parachain_state(|| { - assert!( - pallet_liquidity_pools_gateway::Allowlist::::contains_key( - test_instance.domain(), - test_instance.clone() - ) - ); - }); + // Create an AUSD pool + create_ausd_pool::(pool_id); - let msg = LiquidityPoolMessage::AddPool { pool_id: 123 }; + enable_liquidity_pool_transferability::(currency_id); - let encoded_msg = msg.serialize(); + // Enable LiquidityPools transferability + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + // Changed: Add location which can be converted to LiquidityPoolsWrappedToken + Some(Some(liquidity_pools_transferable_multilocation::( + evm_chain_id, + evm_address, + ))), + Some(CustomMetadata { + // Changed: Allow liquidity_pools transferability + transferability: CrossChainTransferability::LiquidityPools, + pool_currency: true, + ..Default::default() + }) + )); - let gateway_msg = BoundedVec::< - u8, - ::MaxIncomingMessageSize, - >::try_from(encoded_msg) - .unwrap(); + assert_ok!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ) + ); - env.parachain_state_mut(|| { - assert_noop!( - pallet_liquidity_pools_gateway::Pallet::::process_msg( - GatewayOrigin::Domain(test_instance).into(), - gateway_msg, - ), - pallet_liquidity_pools::Error::::InvalidIncomingMessage, - ); - }); - } + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Charlie.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); + }); } -} -mod altair { - use altair_runtime::{xcm::CurrencyIdConvert, PoolPalletIndex}; - use utils::*; + #[test_runtimes([development])] + fn allow_investment_currency_should_fail() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - use super::*; + setup_test(&mut env); - pub const KSM_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(1000); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let currency_id = CurrencyId::ForeignAsset(42); + let ausd_currency_id = AUSD_CURRENCY_ID; - mod utils { - use super::*; + // Should fail if pool does not exist + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); - pub fn register_air() { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(parachains::kusama::altair::ID), - general_key(parachains::kusama::altair::AIR_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() + // Register currency_id with pool_currency set to true + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + AssetMetadata { + name: BoundedVec::default(), + symbol: BoundedVec::default(), + decimals: 12, + location: None, + existential_deposit: 1_000_000, + additional: CustomMetadata { + pool_currency: true, + ..Default::default() + }, }, - }; + Some(currency_id) + )); - assert_ok!(orml_asset_registry::Pallet::::register_asset( + // Create pool + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); + + enable_liquidity_pool_transferability::(ausd_currency_id); + + // Should fail if currency is not liquidityPools transferable + assert_ok!(orml_asset_registry::Pallet::::update_asset( ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) + currency_id, + None, + None, + None, + None, + None, + Some(CustomMetadata { + // Disallow any cross chain transferability + transferability: CrossChainTransferability::None, + // Changed: Allow to be usable as pool currency + pool_currency: true, + ..Default::default() + }), )); - } + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable + ); - pub fn register_ksm() { - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000, - location: Some(VersionedLocation::V4(Location::new(1, Here))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + // Should fail if currency does not have any Location in metadata + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + None, + Some(CustomMetadata { + // Changed: Allow liquidityPools transferability + transferability: CrossChainTransferability::LiquidityPools, + // Still allow to be pool currency + pool_currency: true, + ..Default::default() + }), + )); + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken + ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( + // Should fail if currency does not have LiquidityPoolsWrappedToken location in + // metadata + assert_ok!(orml_asset_registry::Pallet::::update_asset( ::RuntimeOrigin::root(), - meta, - Some(KSM_ASSET_ID) + currency_id, + None, + None, + None, + None, + // Changed: Add some location which cannot be converted to + // LiquidityPoolsWrappedToken + Some(Some(VersionedLocation::V4(Default::default()))), + // No change for transferability required as it is already allowed for + // LiquidityPools + None, )); - } + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken + ); + }); + } + + #[test_runtimes([development])] + fn disallow_investment_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - pub fn air(amount: Balance) -> Balance { - amount * decimals(currency_decimals::NATIVE) - } + setup_test(&mut env); - pub fn ksm(amount: Balance) -> Balance { - amount * decimals(currency_decimals::KSM) - } + env.parachain_state_mut(|| { + let currency_id = AUSD_CURRENCY_ID; + let pool_id = POOL_ID; + let evm_chain_id: u64 = MOONBEAM_EVM_CHAIN_ID; + let evm_address = [1u8; 20]; - pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { - amount * decimals(num_decimals) - } + // Create an AUSD pool + create_ausd_pool::(pool_id); - pub fn air_fee() -> Balance { - fee(currency_decimals::NATIVE) - } + enable_liquidity_pool_transferability::(currency_id); - // The fee associated with transferring KSM tokens - pub fn ksm_fee() -> Balance { - calc_fee(ksm_per_second()) - } - } + // Enable LiquidityPools transferability + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + // Changed: Add location which can be converted to LiquidityPoolsWrappedToken + Some(Some(liquidity_pools_transferable_multilocation::( + evm_chain_id, + evm_address, + ))), + Some(CustomMetadata { + // Changed: Allow liquidity_pools transferability + transferability: CrossChainTransferability::LiquidityPools, + pool_currency: true, + ..Default::default() + }) + )); - mod transfers { - use super::*; + assert_ok!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ) + ); + + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Charlie.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); + }); + } - fn transfer_air_to_sibling(env: &mut FudgeEnv) { - let alice_initial_balance = air(10); - let transfer_amount = air(5); - let air_in_sibling = CurrencyId::ForeignAsset(12); + #[test_runtimes([development])] + fn disallow_investment_currency_should_fail() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - 0 - ); + setup_test(&mut env); - // Register AIR as foreign asset in the sibling parachain - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key(parachains::kusama::altair::AIR_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - }); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let currency_id = CurrencyId::ForeignAsset(42); + let ausd_currency_id = AUSD_CURRENCY_ID; - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(air_in_sibling, &Keyring::Bob.into()), - 0 - ); + // Should fail if pool does not exist + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); - // Register AIR as foreign asset in the sibling parachain - let meta: AssetMetadata = AssetMetadata { - decimals: 18, + // Register currency_id with pool_currency set to true + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + AssetMetadata { name: BoundedVec::default(), symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key(parachains::kusama::altair::AIR_KEY), - ], - ))), + decimals: 12, + location: None, + existential_deposit: 1_000_000, additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() + pool_currency: true, + ..Default::default() }, - }; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(air_in_sibling) - )); - }); - - env.pass(Blocks::ByNumber(1)); - - env.parachain_state_mut(|| { - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - CurrencyId::Native, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); - - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - transfer_amount - ); - - // Verify that the amount transferred is now part of the sibling account here - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - transfer_amount - ); - }); - - env.pass(Blocks::ByNumber(2)); - - env.sibling_state_mut(|| { - let current_balance = - orml_tokens::Pallet::::free_balance(air_in_sibling, &Keyring::Bob.into()); + }, + Some(currency_id) + )); - // Verify that Keyring::Bob now has (amount transferred - fee) - assert_eq!(current_balance, transfer_amount - fee(18)); + // Create pool + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); - // Sanity check for the actual amount Keyring::Bob ends up with - assert_eq!(current_balance, 4993570400000000000); - }); - } + enable_liquidity_pool_transferability::(ausd_currency_id); - #[test_runtimes([altair])] - fn test_air_transfers_to_and_from_sibling() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(air(10))) - .storage(), + // Should fail if currency is not liquidityPools transferable + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + None, + Some(CustomMetadata { + // Disallow any cross chain transferability + transferability: CrossChainTransferability::None, + // Changed: Allow to be usable as pool currency + pool_currency: true, + ..Default::default() + }), + )); + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable ); - setup_xcm(&mut env); - - // In order to be able to transfer AIR from Sibling to Altair, we need to first - // send AIR from Altair to Sibling, or else it fails since it'd be like Sibling - // had minted AIR on their side. - transfer_air_to_sibling(&mut env); - - let alice_initial_balance = air(5); - let bob_initial_balance = air(5) - air_fee(); - let transfer_amount = air(1); + // Should fail if currency does not have any Location in metadata + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + None, + Some(CustomMetadata { + // Changed: Allow liquidityPools transferability + transferability: CrossChainTransferability::LiquidityPools, + // Still allow to be pool currency + pool_currency: true, + ..Default::default() + }), + )); + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken + ); - // Note: This asset was registered in `transfer_air_to_sibling` - let air_in_sibling = CurrencyId::ForeignAsset(12); + // Should fail if currency does not have LiquidityPoolsWrappedToken location in + // metadata + assert_ok!(orml_asset_registry::Pallet::::update_asset( + ::RuntimeOrigin::root(), + currency_id, + None, + None, + None, + None, + // Changed: Add some location which cannot be converted to + // LiquidityPoolsWrappedToken + Some(Some(VersionedLocation::V4(Default::default()))), + // No change for transferability required as it is already allowed for + // LiquidityPools + None, + )); + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken + ); + }); + } - env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - ); - }); + #[test_runtimes([development])] + fn schedule_upgrade() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.sibling_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::PARA_ID - )), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::free_balance(air_in_sibling, &Keyring::Bob.into()), - bob_initial_balance - ); + setup_test(&mut env); - assert_ok!(orml_xtokens::Pallet::::transfer( + env.parachain_state_mut(|| { + // Only Root can call `schedule_upgrade` + assert_noop!( + pallet_liquidity_pools::Pallet::::schedule_upgrade( RawOrigin::Signed(Keyring::Bob.into()).into(), - air_in_sibling, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Alice.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + MOONBEAM_EVM_CHAIN_ID, + [7; 20] + ), + BadOrigin + ); - // Confirm that Bobs's balance is initial balance - amount transferred - assert_eq!( - orml_tokens::Pallet::::free_balance(air_in_sibling, &Keyring::Bob.into()), - bob_initial_balance - transfer_amount - ); - }); + // Now it finally works + assert_ok!(pallet_liquidity_pools::Pallet::::schedule_upgrade( + ::RuntimeOrigin::root(), + MOONBEAM_EVM_CHAIN_ID, + [7; 20] + )); + }); + } - env.pass(Blocks::ByNumber(3)); + #[test_runtimes([development])] + fn cancel_upgrade() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.parachain_state_mut(|| { - // Verify that Keyring::Alice now has initial balance + amount transferred - fee - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance + transfer_amount - air_fee(), - ); - }); - } + setup_test(&mut env); - #[test_runtimes([altair])] - fn transfer_ausd_to_altair() { - let mut env = FudgeEnv::::default(); + env.parachain_state_mut(|| { + // Only Root can call `cancel_upgrade` + assert_noop!( + pallet_liquidity_pools::Pallet::::cancel_upgrade( + RawOrigin::Signed(Keyring::Bob.into()).into(), + MOONBEAM_EVM_CHAIN_ID, + [7; 20] + ), + BadOrigin + ); - setup_xcm(&mut env); + // Now it finally works + assert_ok!(pallet_liquidity_pools::Pallet::::cancel_upgrade( + ::RuntimeOrigin::root(), + MOONBEAM_EVM_CHAIN_ID, + [7; 20] + )); + }); + } - let alice_initial_balance = ausd(10); - let transfer_amount = ausd(7); + #[test_runtimes([development])] + fn update_tranche_token_metadata() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.sibling_state_mut(|| { - register_ausd::(); + setup_test(&mut env); - assert_ok!(orml_tokens::Pallet::::deposit( - AUSD_CURRENCY_ID, - &Keyring::Alice.into(), - alice_initial_balance - )); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + // NOTE: Default pool admin is BOB + create_ausd_pool::(pool_id); - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - 0 - ); - }); + // Missing tranche token should throw + let nonexistent_tranche = [71u8; 16]; - env.parachain_state_mut(|| { - register_ausd::(); + assert_noop!( + pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( + RawOrigin::Signed(Keyring::Alice.into()).into(), + pool_id, + nonexistent_tranche, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ), + pallet_liquidity_pools::Error::::TrancheNotFound + ); - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - 0, - ); - }); + let tranche_id = default_tranche_id::(pool_id); - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - &Keyring::Alice.into() - ), - ausd(10), - ); - assert_ok!(orml_xtokens::Pallet::::transfer( + // Moving the update to another domain can be called by anyone + assert_ok!( + pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( RawOrigin::Signed(Keyring::Alice.into()).into(), - AUSD_CURRENCY_ID, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + pool_id, + tranche_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ) + ); - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - &Keyring::Alice.into() - ), - alice_initial_balance - transfer_amount - ); + // Edge case: Should throw if tranche exists but metadata does not exist + let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_id); - // Verify that the amount transferred is now part of the altair parachain - // account here - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - transfer_amount - ); - }); + orml_asset_registry::Metadata::::remove(tranche_currency_id); - env.pass(Blocks::ByNumber(3)); + assert_noop!( + pallet_liquidity_pools::Pallet::::update_tranche_token_metadata( + RawOrigin::Signed(POOL_ADMIN.into()).into(), + pool_id, + tranche_id, + Domain::EVM(MOONBEAM_EVM_CHAIN_ID), + ), + pallet_liquidity_pools::Error::::TrancheMetadataNotFound + ); + }); + } +} - env.parachain_state_mut(|| { - // Verify that Keyring::Bob now has initial balance + amount transferred - fee - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - transfer_amount - ausd_fee() - ); - }); - } +mod foreign_investments { + use super::*; - fn transfer_ksm_from_relay_chain( - env: &mut FudgeEnv, - transfer_amount: Balance, - currency_id: CurrencyId, - meta: AssetMetadata, - ) { - env.parachain_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(currency_id), - )); + mod same_currencies { + use super::*; - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &Keyring::Bob.into()), - 0 - ); - }); + #[test_runtimes([development])] + fn increase_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![( + GLMR_CURRENCY_ID, + DEFAULT_BALANCE_GLMR, + )])) + .storage(), + ); - env.relay_state_mut(|| { - assert_ok!( - pallet_balances::Pallet::>::force_set_balance( - as frame_system::Config>::RuntimeOrigin::root(), - as frame_system::Config>::Lookup::unlookup( - Keyring::Alice.id() - ), - transfer_amount * 2, - ) - ); + setup_test(&mut env); - assert_ok!( - pallet_xcm::Pallet::>::force_xcm_version( - as frame_system::Config>::RuntimeOrigin::root(), - Box::new(Location::new( - 0, - Junction::Parachain(T::FudgeHandle::PARA_ID), - )), - XCM_VERSION, - ) - ); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - assert_ok!( - pallet_xcm::Pallet::>::reserve_transfer_assets( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Box::new(Parachain(T::FudgeHandle::PARA_ID).into()), - Box::new( - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - .into() - ), - Box::new((Here, transfer_amount).into()), - 0 - ) - ); - }); + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - env.pass(Blocks::ByNumber(2)); + // Set permissions and execute initial investment + do_initial_increase_investment::(pool_id, amount, investor.clone(), currency_id); - env.parachain_state(|| { + // Verify the order was updated to the amount assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &Keyring::Bob.into()), - 1991963000000 // Comes from `transfer_amount - fee(meta.decimals)` with noise + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + amount ); + + // Increasing again should just bump invest_amount + let msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); }); } - #[test_runtimes([altair])] - fn transfer_ksm_to_and_from_relay_chain() { - let mut env = FudgeEnv::::default(); - - let transfer_amount: Balance = ksm(2); - let currency_id = CurrencyId::ForeignAsset(3001); - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000, - location: Some(VersionedLocation::V4(Location::new(1, Here))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - - // First we need some KSM on Altair - transfer_ksm_from_relay_chain(&mut env, transfer_amount, currency_id, meta.clone()); + #[test_runtimes([development])] + fn decrease_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - let currency_id = CurrencyId::ForeignAsset(3001); + setup_test(&mut env); env.parachain_state_mut(|| { - assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new(1, Junctions::Here)), - XCM_VERSION, - )); - - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Bob.into()).into(), + let pool_id = POOL_ID; + let invest_amount: u128 = 10 * decimals(12); + let decrease_amount = invest_amount / 3; + let final_amount = invest_amount - decrease_amount; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id: CurrencyId = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + + // Set permissions and execute initial investment + do_initial_increase_investment::( + pool_id, + invest_amount, + investor.clone(), currency_id, - ksm(1), - Box::new( - Location::new( - 1, - Junction::AccountId32 { - id: Keyring::Bob.into(), - network: None, - } - ) - .into() + ); + + // Mock incoming decrease message + let msg = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: decrease_amount, + }; + + // Expect failure if transferability is disabled since this is required for + // preparing the `ExecutedDecreaseInvest` message. + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg.clone() ), - WeightLimit::Limited(4_000_000_000.into()) - )); - }); + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable + ); + enable_liquidity_pool_transferability::(currency_id); - env.pass(Blocks::ByNumber(2)); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - env.relay_state_mut(|| { + // Verify investment was decreased into investment account assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Bob.into() + orml_tokens::Pallet::::balance( + currency_id, + &default_investment_account::() ), - 999989698923 + final_amount + ); + // Since the investment was done in the pool currency, the decrement happens + // synchronously and thus it must be burned from investor's holdings + assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == pallet_investments::Event::::InvestOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: final_amount + } + .into())); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == orml_tokens::Event::::Withdrawn { + currency_id, + who: investor.clone(), + amount: decrease_amount + } + .into())); + assert_eq!( + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + final_amount ); }); } - #[test_runtimes([altair])] - fn transfer_foreign_sibling_to_altair() { + #[test_runtimes([development])] + fn cancel_invest_order() { let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(air(10))) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - setup_xcm(&mut env); + setup_test(&mut env); - let sibling_asset_id = CurrencyId::ForeignAsset(1); - let asset_location = Location::new( - 1, - [Parachain(T::FudgeHandle::SIBLING_ID), general_key(&[0, 1])], - ); - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(asset_location)), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(XcmMetadata { - // We specify a custom fee_per_second and verify below that this value is - // used when XCM transfer fees are charged for this token. - fee_per_second: Some(8420000000000000000), - }), - ..CustomMetadata::default() - }, - }; - let transfer_amount = foreign(1, meta.decimals); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let invest_amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()), - 0 + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + + // Set permissions and execute initial investment + do_initial_increase_investment::( + pool_id, + invest_amount, + investor.clone(), + currency_id, ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(CurrencyId::Native), - )); - }); - env.parachain_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(sibling_asset_id) - )); - }); + // Verify investment account holds funds before cancelling + assert_eq!( + orml_tokens::Pallet::::balance( + currency_id, + &default_investment_account::() + ), + invest_amount + ); - env.sibling_state_mut(|| { - assert_ok!(pallet_balances::Pallet::::force_set_balance( - ::RuntimeOrigin::root(), - Keyring::Alice.id().into(), - transfer_amount * 2, - )); + // Mock incoming cancel message + let msg = LiquidityPoolMessage::CancelInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + }; - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - CurrencyId::Native, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() + // Expect failure if transferability is disabled since this is required for + // preparing the `ExecutedDecreaseInvest` message. + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg.clone() ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); - - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - transfer_amount + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable ); - }); - env.pass(Blocks::ByNumber(3)); + enable_liquidity_pool_transferability::(currency_id); - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - // Verify that Keyring::Bob now has initial balance + amount transferred - fee + // Verify investment was entirely drained from investment account assert_eq!( - bob_balance, - transfer_amount - - calc_fee( - xcm_metadata(meta.additional.transferability) - .unwrap() - .fee_per_second - .unwrap() - ) + orml_tokens::Pallet::::balance( + currency_id, + &default_investment_account::() + ), + 0 + ); + // Since the investment was done in the pool currency, the decrement happens + // synchronously and thus it must be burned from investor's holdings + assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == pallet_investments::Event::::InvestOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: 0 + } + .into())); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == orml_tokens::Event::::Withdrawn { + currency_id, + who: investor.clone(), + amount: invest_amount + } + .into())); + assert_eq!( + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + 0 ); - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 993264000000000000); }); } - #[test_runtimes([altair])] - fn transfer_wormhole_usdc_karura_to_altair() { - let mut env = FudgeEnv::::from_storage( - Default::default(), - Default::default(), + #[test_runtimes([development])] + fn collect_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(air(10))) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - setup_xcm(&mut env); + setup_test(&mut env); - let usdc_asset_id = CurrencyId::ForeignAsset(39); - let asset_location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - general_key("0x02f3a00dd12f644daec907013b16eb6d14bf1c4cb4".as_bytes()), - ], - ); - let meta: AssetMetadata = AssetMetadata { - decimals: 6, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1, - location: Some(VersionedLocation::V4(asset_location)), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - let transfer_amount = foreign(12, meta.decimals); - let alice_initial_balance = transfer_amount * 100; - - env.sibling_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(usdc_asset_id) - )); - assert_ok!(orml_tokens::Pallet::::deposit( - usdc_asset_id, - &Keyring::Alice.into(), - alice_initial_balance + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); + enable_liquidity_pool_transferability::(currency_id); + + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + let investment_currency_id: CurrencyId = default_investment_id::().into(); + // Set permissions and execute initial investment + do_initial_increase_investment::(pool_id, amount, investor.clone(), currency_id); + let events_before_collect = frame_system::Pallet::::events(); + + // Process and fulfill order + // NOTE: Without this step, the order id is not cleared and + // `Event::InvestCollectedForNonClearedOrderId` be dispatched + assert_ok!(pallet_investments::Pallet::::process_invest_orders( + default_investment_id::() )); + + // Tranche tokens will be minted upon fulfillment assert_eq!( - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Alice.into()), - alice_initial_balance + orml_tokens::Pallet::::total_issuance(investment_currency_id), + 0 ); + assert_ok!(pallet_investments::Pallet::::invest_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::one(), + } + )); assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - air(10) + orml_tokens::Pallet::::total_issuance(investment_currency_id), + amount ); - }); - env.parachain_state_mut(|| { - // First, register the asset in centrifuge - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(usdc_asset_id) + // Mock collection message msg + let msg = LiquidityPoolMessage::CollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg )); - }); - env.sibling_state_mut(|| { - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - usdc_asset_id, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000.into()), - )); + // Remove events before collect execution + let events_since_collect: Vec<_> = frame_system::Pallet::::events() + .into_iter() + .filter(|e| !events_before_collect.contains(e)) + .collect(); - // Confirm that Alice's balance is initial balance - amount transferred + // Verify investment was transferred to the domain locator assert_eq!( - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Alice.into()), - alice_initial_balance - transfer_amount + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator + ), + amount ); - }); - env.pass(Blocks::ByNumber(3)); - - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Bob.into()); - - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 11993571); - }); - } - } - - mod asset_registry { - use super::*; - - #[test_runtimes([altair])] - fn register_air_works() { - let mut env = FudgeEnv::::default(); - - env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 0, - general_key(parachains::kusama::altair::AIR_KEY), - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + // Order should have been cleared by fulfilling investment + assert_eq!( + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + 0 + ); + assert!(!events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::InvestCollectedForNonClearedOrderId { + investment_id: default_investment_id::(), + who: investor.clone(), + } + .into() + })); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - }); - } + // Order should not have been updated since everything is collected + assert!(!events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::InvestOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: 0, + } + .into() + })); - #[test_runtimes([altair])] - fn register_foreign_asset_works() { - let mut env = FudgeEnv::::default(); + // Order should have been fully collected + assert!(events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::InvestOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![0], + who: investor.clone(), + collection: InvestCollection:: { + payout_investment_invest: amount, + remaining_investment_invest: 0, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); - env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - general_key(parachains::kusama::karura::AUSD_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + let sender = ::Sender::get(); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::ForeignAsset(42)) - )); + // Clearing of foreign InvestState should be dispatched + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: amount, + tranche_tokens_payout: amount, + remaining_invest_amount: 0, + }, + } + .into() + })); }); } - // Verify that registering tranche tokens is not allowed through extrinsics - #[test_runtimes([altair])] - fn register_tranche_asset_blocked() { - let mut env = FudgeEnv::::default(); + #[test_runtimes([development])] + fn partially_collect_investment_for_through_investments() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [Parachain(2000), general_key(&[42])], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + setup_test(&mut env); - // It fails with `BadOrigin` even when submitted with `Origin::root` since we - // only allow for tranche tokens to be registered through the pools pallet. - let asset_id = CurrencyId::Tranche(42, [42u8; 16]); - assert_noop!( - orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(asset_id) - ), - BadOrigin + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let invest_amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_investment::( + pool_id, + invest_amount, + investor.clone(), + currency_id, ); - }); - } - } - - mod currency_id_convert { - use super::*; - - #[test_runtimes([altair])] - fn convert_air() { - let mut env = FudgeEnv::::default(); - - assert_eq!(parachains::kusama::altair::AIR_KEY.to_vec(), vec![0, 1]); + enable_liquidity_pool_transferability::(currency_id); + let investment_currency_id: CurrencyId = default_investment_id::().into(); - env.parachain_state_mut(|| { - // The way AIR is represented relative within the Altair runtime - let air_location_inner: Location = - Location::new(0, general_key(parachains::kusama::altair::AIR_KEY)); + assert!( + !pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) + ); - // register air - register_air::(); + // Process 50% of investment at 25% rate, i.e. 1 pool currency = 4 tranche + // tokens + assert_ok!(pallet_investments::Pallet::::process_invest_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::invest_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::from_percent(50), + price: Ratio::checked_from_rational(1, 4).unwrap(), + } + )); - assert_eq!( - >::convert(air_location_inner), - Some(CurrencyId::Native), + // Pre collect assertions + assert!( + pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) ); - // The canonical way AIR is represented out in the wild - let air_location_canonical: Location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key(parachains::kusama::altair::AIR_KEY), - ], + // Collecting through Investments should denote amounts and transition + // state + assert_ok!(pallet_investments::Pallet::::collect_investments_for( + RawOrigin::Signed(Keyring::Alice.into()).into(), + investor.clone(), + default_investment_id::() + )); + assert!( + !pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) ); + // Tranche Tokens should still be transferred to collected to + // domain locator account already assert_eq!( - >::convert(CurrencyId::Native), - Some(air_location_canonical) - ) - }); - } + orml_tokens::Pallet::::balance(investment_currency_id, &investor), + 0 + ); + assert_eq!( + orml_tokens::Pallet::::balance( + investment_currency_id, + &sending_domain_locator + ), + invest_amount * 2 + ); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::InvestOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![0], + who: investor.clone(), + collection: InvestCollection:: { + payout_investment_invest: invest_amount * 2, + remaining_investment_invest: invest_amount / 2, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); - /// Verify that Tranche tokens are not handled by the CurrencyIdConvert - /// since we don't allow Tranche tokens to be transferable through XCM. - #[test_runtimes([altair])] - fn convert_tranche() { - let mut env = FudgeEnv::::default(); + let sender = ::Sender::get(); - let tranche_currency = CurrencyId::Tranche(401, [0; 16]); - let tranche_id = - WeakBoundedVec::>::force_from(tranche_currency.encode(), None); - let tranche_multilocation = Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - PalletInstance(PoolPalletIndex::get()), - GeneralKey { - length: tranche_id.len() as u8, - data: vec_to_fixed_array(tranche_id), - }, - ], - ); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: pallet_liquidity_pools::Message::ExecutedCollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: invest_amount / 2, + tranche_tokens_payout: invest_amount * 2, + remaining_invest_amount: invest_amount / 2, + }, + } + .into() + })); - env.parachain_state_mut(|| { + // Process rest of investment at 50% rate (1 pool currency = 2 tranche tokens) + assert_ok!(pallet_investments::Pallet::::process_invest_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::invest_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::checked_from_rational(1, 2).unwrap(), + } + )); + // Order should have been cleared by fulfilling investment assert_eq!( - >::convert(tranche_multilocation), - None, + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + 0 ); - }); - - env.parachain_state_mut(|| { assert_eq!( - >::convert(tranche_currency), - None - ) - }); - } - - #[test_runtimes([altair])] - fn convert_ausd() { - let mut env = FudgeEnv::::default(); - - env.parachain_state_mut(|| { - assert_eq!(parachains::kusama::karura::AUSD_KEY, &[0, 129]); - - let ausd_location: Location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - general_key(parachains::kusama::karura::AUSD_KEY), - ], + orml_tokens::Pallet::::total_issuance(investment_currency_id), + invest_amount * 3 ); - register_ausd::(); + // Collect remainder through Investments + assert_ok!(pallet_investments::Pallet::::collect_investments_for( + RawOrigin::Signed(Keyring::Alice.into()).into(), + investor.clone(), + default_investment_id::() + )); + assert!( + !pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) + ); + // Tranche Tokens should be transferred to collected to + // domain locator account already + let amount_tranche_tokens = invest_amount * 3; assert_eq!( - >::convert(ausd_location.clone()), - Some(AUSD_CURRENCY_ID), + orml_tokens::Pallet::::total_issuance(investment_currency_id), + amount_tranche_tokens + ); + assert!( + orml_tokens::Pallet::::balance(investment_currency_id, &investor).is_zero() ); - assert_eq!( - >::convert(AUSD_CURRENCY_ID), - Some(ausd_location) - ) - }); - } - - #[test_runtimes([altair])] - fn convert_ksm() { - let mut env = FudgeEnv::::default(); - - let ksm_location: Location = Location::parent().into(); + orml_tokens::Pallet::::balance( + investment_currency_id, + &sending_domain_locator + ), + amount_tranche_tokens + ); + assert!(!frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::InvestCollectedForNonClearedOrderId { + investment_id: default_investment_id::(), + who: investor.clone(), + } + .into() + })); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::InvestOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![1], + who: investor.clone(), + collection: InvestCollection:: { + payout_investment_invest: invest_amount, + remaining_investment_invest: 0, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); - env.parachain_state_mut(|| { - register_ksm::(); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: invest_amount / 2, + tranche_tokens_payout: invest_amount, + remaining_invest_amount: 0, + }, + } + .into() + })); - assert_eq!( - >::convert(ksm_location.clone()), - Some(KSM_ASSET_ID), + // Should fail to collect if `InvestmentState` does not + // exist + let msg = LiquidityPoolMessage::CollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + ), + pallet_foreign_investments::Error::::InfoNotFound ); - - assert_eq!( - >::convert(KSM_ASSET_ID), - Some(ksm_location) - ) }); } - #[test_runtimes([altair])] - fn convert_unkown_multilocation() { - let mut env = FudgeEnv::::default(); - - let unknown_location: Location = - Location::new(1, [Parachain(T::FudgeHandle::PARA_ID), general_key(&[42])]); - - env.parachain_state_mut(|| { - assert!(>::convert(unknown_location).is_none()); - }); - } + #[test_runtimes([development])] + fn increase_redeem_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - #[test_runtimes([altair])] - fn convert_unsupported_currency() { - let mut env = FudgeEnv::::default(); + setup_test(&mut env); env.parachain_state_mut(|| { - assert_eq!( - >::convert(CurrencyId::Tranche( - 0, - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - )), - None - ) - }); - } - } -} - -mod centrifuge { - use centrifuge_runtime::xcm::CurrencyIdConvert; - use utils::*; - - use super::*; - - mod utils { - use super::*; - - /// The test asset id attributed to DOT - pub const DOT_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(91); - - pub const LP_ETH_USDC: CurrencyId = CurrencyId::ForeignAsset(100_001); - - pub const USDC: CurrencyId = CurrencyId::ForeignAsset(6); - - /// An Asset that is NOT XCM transferable - pub const NO_XCM_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(401); - - /// Register DOT in the asset registry. - /// It should be executed within an externalities environment. - pub fn register_dot() { - let meta: AssetMetadata = AssetMetadata { - decimals: 10, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 100_000_000, - location: Some(VersionedLocation::V4(Location::parent())), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(DOT_ASSET_ID) - )); - } - - pub fn register_lp_eth_usdc() { - let meta: AssetMetadata = AssetMetadata { - decimals: 6, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000, - location: Some(VersionedLocation::V4(Location::new( - 0, - [ - PalletInstance(103), - GlobalConsensus(NetworkId::Ethereum { chain_id: 1 }), - AccountKey20 { - network: None, - key: hex_literal::hex!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), - }, - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::LiquidityPools, - ..CustomMetadata::default() - }, - }; - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(LP_ETH_USDC) - )); - } - - pub fn register_usdc() { - let meta: AssetMetadata = AssetMetadata { - decimals: 6, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Junction::Parachain(1000), - Junction::PalletInstance(50), - Junction::GeneralIndex(1337), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(USDC) - )); - } - - /// Register CFG in the asset registry. - /// It should be executed within an externalities environment. - pub fn register_cfg(para_id: u32) { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(para_id), - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - } + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - /// Register CFG in the asset registry as XCM v2, just like it is in - /// production. It should be executed within an externalities - /// environment. - pub fn register_cfg_v2() { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V2(staging_xcm::v2::MultiLocation::new( - 1, - staging_xcm::v2::Junctions::X2( - staging_xcm::v2::Junction::Parachain(T::FudgeHandle::PARA_ID), - staging_xcm::v2::Junction::GeneralKey( - WeakBoundedVec::>::force_from( - parachains::polkadot::centrifuge::CFG_KEY.into(), - None, - ), - ), - ), - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - } + // Set permissions and execute initial redemption + do_initial_increase_redemption::(pool_id, amount, investor.clone(), currency_id); - /// Register a token whose `CrossChainTransferability` does NOT include - /// XCM. - pub fn register_no_xcm_token() { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: None, - additional: CustomMetadata { - transferability: CrossChainTransferability::LiquidityPools, - ..CustomMetadata::default() - }, - }; + // Verify amount was noted in the corresponding order + assert_eq!( + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), + ) + .amount, + amount + ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(NO_XCM_ASSET_ID) - )); + // Increasing again should just bump redeeming amount + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), + amount + )); + let msg = LiquidityPoolMessage::IncreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + }); } - // The fee associated with transferring DOT tokens - pub fn dot_fee() -> Balance { - fee(10) - } + #[test_runtimes([development])] + fn decrease_redeem_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - pub fn lp_eth_usdc_fee() -> Balance { - fee(6) - } + setup_test(&mut env); - pub fn usdc_fee() -> Balance { - fee(6) - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let redeem_amount = 10 * decimals(12); + let decrease_amount = redeem_amount / 3; + let final_amount = redeem_amount - decrease_amount; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - pub fn dot(amount: Balance) -> Balance { - amount * decimals(10) - } + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - pub fn lp_eth_usdc(amount: Balance) -> Balance { - amount * decimals(6) - } + // Set permissions and execute initial redemption + do_initial_increase_redemption::( + pool_id, + redeem_amount, + investor.clone(), + currency_id, + ); - pub fn usdc(amount: Balance) -> Balance { - amount * decimals(6) - } + // Verify the corresponding redemption order id is 0 + assert_eq!( + pallet_investments::Pallet::::invest_order_id(investment_id::( + pool_id, + default_tranche_id::(pool_id) + )), + 0 + ); - pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { - amount * decimals(num_decimals) - } + // Mock incoming decrease message + let msg = LiquidityPoolMessage::DecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: decrease_amount, + }; - pub fn transfer_dot_from_relay_chain(env: &mut FudgeEnv) { - let alice_initial_dot = dot(10); - let transfer_amount: Balance = dot(3); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - env.parachain_state_mut(|| { - register_dot::(); + // Verify investment was decreased into investment account + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &default_investment_account::(), + ), + final_amount + ); + // Tokens should have been transferred from investor's wallet to domain's + // sovereign account assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &investor + ), 0 ); - }); + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator + ), + decrease_amount + ); - env.relay_state_mut(|| { - assert_ok!( - pallet_balances::Pallet::>::force_set_balance( - as frame_system::Config>::RuntimeOrigin::root(), - as frame_system::Config>::Lookup::unlookup( - Keyring::Alice.id() - ), - alice_initial_dot, + // Order should have been updated + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == pallet_investments::Event::::RedeemOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: final_amount + } + .into())); + assert_eq!( + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), ) + .amount, + final_amount ); - assert_ok!( - pallet_xcm::Pallet::>::force_xcm_version( - as frame_system::Config>::RuntimeOrigin::root(), - Box::new(Location::new( - 0, - Junction::Parachain(T::FudgeHandle::PARA_ID), - )), - XCM_VERSION, - ) + let sender = ::Sender::get(); + + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedDecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + tranche_tokens_payout: decrease_amount, + remaining_redeem_amount: final_amount, + }, + } + .into() + })); + }); + } + + #[test_runtimes([development])] + fn cancel_redeem_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); + + setup_test(&mut env); + + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let redeem_amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); + + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + + // Set permissions and execute initial redemption + do_initial_increase_redemption::( + pool_id, + redeem_amount, + investor.clone(), + currency_id, ); - assert_ok!( - pallet_xcm::Pallet::>::reserve_transfer_assets( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Box::new(Parachain(T::FudgeHandle::PARA_ID).into()), - Box::new( - Junction::AccountId32 { - network: None, - id: Keyring::Alice.into(), - } - .into() - ), - Box::new((Here, transfer_amount).into()), - 0 - ) + // Verify the corresponding redemption order id is 0 + assert_eq!( + pallet_investments::Pallet::::invest_order_id(investment_id::( + pool_id, + default_tranche_id::(pool_id) + )), + 0 ); + // Mock incoming decrease message + let msg = LiquidityPoolMessage::CancelRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + }; + + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + + // Verify investment was decreased into investment account assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &default_investment_account::(), ), - 69867666991 // Comes from alice_initial_dot - transfer_amount with noise + 0 + ); + // Tokens should have been transferred from investor's wallet to domain's + // sovereign account + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &investor + ), + 0 + ); + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator + ), + redeem_amount ); - }); - - env.pass(Blocks::ByNumber(2)); - env.parachain_state(|| { + // Order should have been updated + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == pallet_investments::Event::::RedeemOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: 0 + } + .into())); assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - 29919630000 // Comes from `transfer_amount - dot_fee()` with some noise + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), + ) + .amount, + 0 ); }); } - } - mod asset_registry { - use super::*; + #[test_runtimes([development])] + fn fully_collect_redeem_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - #[test_runtimes([centrifuge])] - fn register_cfg_works() { - let mut env = FudgeEnv::::default(); + setup_test(&mut env); env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 0, - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } + .into_account_truncating(); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - }); - } + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - #[test_runtimes([centrifuge])] - fn register_foreign_asset_works() { - let mut env = FudgeEnv::::default(); + // Set permissions and execute initial investment + do_initial_increase_redemption::(pool_id, amount, investor.clone(), currency_id); + let events_before_collect = frame_system::Pallet::::events(); - env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(parachains::polkadot::acala::ID), - general_key(parachains::polkadot::acala::AUSD_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + // Fund the pool account with sufficient pool currency, else redemption cannot + // swap tranche tokens against pool currency + assert_ok!(orml_tokens::Pallet::::mint_into( + currency_id, + &pool_account, + amount + )); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::ForeignAsset(42)) + // Process and fulfill order + // NOTE: Without this step, the order id is not cleared and + // `Event::RedeemCollectedForNonClearedOrderId` be dispatched + assert_ok!(pallet_investments::Pallet::::process_redeem_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::one(), + } )); - }); - } - // Verify that registering tranche tokens is not allowed through extrinsics - #[test_runtimes([centrifuge])] - fn register_tranche_asset_blocked() { - let mut env = FudgeEnv::::default(); + // Enable liquidity pool transferability + enable_liquidity_pool_transferability::(currency_id); - env.parachain_state_mut(|| { - let meta: AssetMetadata = AssetMetadata { - decimals: 12, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [Parachain(2000), general_key(&[42])], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, + // Mock collection message msg + let msg = LiquidityPoolMessage::CollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), }; - // It fails with `BadOrigin` even when submitted with `Origin::root` since we - // only allow for tranche tokens to be registered through the pools pallet. - let asset_id = CurrencyId::Tranche(42, [42u8; 16]); - assert_noop!( - orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(asset_id) - ), - BadOrigin - ); - }); - } - } - - mod currency_id_convert { - use super::*; - - #[test_runtimes([centrifuge])] - fn convert_cfg() { - let mut env = FudgeEnv::::default(); - - assert_eq!(parachains::polkadot::centrifuge::CFG_KEY, &[0, 1]); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - env.parachain_state_mut(|| { - // The way CFG is represented relative within the Centrifuge runtime - let cfg_location_inner: Location = - Location::new(0, general_key(parachains::polkadot::centrifuge::CFG_KEY)); + // Remove events before collect execution + let events_since_collect: Vec<_> = frame_system::Pallet::::events() + .into_iter() + .filter(|e| !events_before_collect.contains(e)) + .collect(); - register_cfg::(T::FudgeHandle::PARA_ID); + // Verify collected redemption was burned from investor + assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == orml_tokens::Event::::Withdrawn { + currency_id, + who: investor.clone(), + amount + } + .into())); + // Order should have been cleared by fulfilling redemption assert_eq!( - >::convert(cfg_location_inner), - Some(CurrencyId::Native), + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), + ) + .amount, + 0 ); + assert!(!events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemCollectedForNonClearedOrderId { + investment_id: default_investment_id::(), + who: investor.clone(), + } + .into() + })); - // The canonical way CFG is represented out in the wild - let cfg_location_canonical: Location = Location::new( - 1, - [ - Parachain(parachains::polkadot::centrifuge::ID), - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ], - ); + // Order should not have been updated since everything is collected + assert!(!events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemOrderUpdated { + investment_id: default_investment_id::(), + submitted_at: 0, + who: investor.clone(), + amount: 0, + } + .into() + })); - assert_eq!( - >::convert(CurrencyId::Native), - Some(cfg_location_canonical) - ) + // Order should have been fully collected + assert!(events_since_collect.iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![0], + who: investor.clone(), + collection: RedeemCollection:: { + payout_investment_redeem: amount, + remaining_investment_redeem: 0, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); + + let sender = ::Sender::get(); + + // Clearing of foreign RedeemState should be dispatched + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: amount, + tranche_tokens_payout: amount, + remaining_redeem_amount: 0, + }, + } + .into() + })); }); } - /// Verify that even with CFG registered in the AssetRegistry with a XCM - /// v2 Location, that `CurrencyIdConvert` can look it up given an - /// identical location in XCM v3. - #[test_runtimes([centrifuge])] - fn convert_cfg_xcm_v2() { - let mut env = FudgeEnv::::default(); + #[test_runtimes([development])] + fn partially_collect_redemption_for_through_investments() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_eq!(parachains::polkadot::centrifuge::CFG_KEY, &[0, 1]); + setup_test(&mut env); env.parachain_state_mut(|| { - // Registered as xcm v2 - register_cfg_v2::(); - - // The way CFG is represented relative within the Centrifuge runtime in xcm v3 - let cfg_location_inner: Location = - Location::new(0, general_key(parachains::polkadot::centrifuge::CFG_KEY)); - - assert_eq!( - >::convert(cfg_location_inner), - Some(CurrencyId::Native), + let pool_id = POOL_ID; + let redeem_amount = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } + .into_account_truncating(); + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_redemption::( + pool_id, + redeem_amount, + investor.clone(), + currency_id, ); + enable_liquidity_pool_transferability::(currency_id); - // The canonical way CFG is represented out in the wild - let cfg_location_canonical: Location = Location::new( - 1, - [ - Parachain(parachains::polkadot::centrifuge::ID), - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ], + // Fund the pool account with sufficient pool currency, else redemption cannot + // swap tranche tokens against pool currency + assert_ok!(orml_tokens::Pallet::::mint_into( + currency_id, + &pool_account, + redeem_amount + )); + assert!( + !pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) ); - assert_eq!( - >::convert(CurrencyId::Native), - Some(cfg_location_canonical) - ) - }); - } - - /// Verify that a registered token that is NOT XCM transferable is - /// filtered out by CurrencyIdConvert as expected. - #[test_runtimes([centrifuge])] - fn convert_no_xcm_token() { - let mut env = FudgeEnv::::default(); - - env.parachain_state_mut(|| { - register_no_xcm_token::(); + // Process 50% of redemption at 25% rate, i.e. 1 pool currency = 4 tranche + // tokens + assert_ok!(pallet_investments::Pallet::::process_redeem_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::from_percent(50), + price: Ratio::checked_from_rational(1, 4).unwrap(), + } + )); - assert_eq!( - >::convert(NO_XCM_ASSET_ID), - None - ) - }); - } + // Pre collect assertions + assert!( + pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) + ); - #[test_runtimes([centrifuge])] - fn convert_dot() { - let mut env = FudgeEnv::::default(); + // Collecting through investments should denote amounts and transition + // state + assert_ok!(pallet_investments::Pallet::::collect_redemptions_for( + RawOrigin::Signed(Keyring::Alice.into()).into(), + investor.clone(), + default_investment_id::() + )); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![0], + who: investor.clone(), + collection: RedeemCollection:: { + payout_investment_redeem: redeem_amount / 8, + remaining_investment_redeem: redeem_amount / 2, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); - let dot_location: Location = Location::parent(); + let sender = ::Sender::get(); - env.parachain_state_mut(|| { - register_dot::(); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: redeem_amount / 8, + tranche_tokens_payout: redeem_amount / 2, + remaining_redeem_amount: redeem_amount / 2, + }, + } + .into() + })); + assert!( + !pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) + ); + // Since foreign currency is pool currency, the swap is immediately fulfilled + // and ExecutedCollectRedeem dispatched + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == orml_tokens::Event::::Withdrawn { + currency_id, + who: investor.clone(), + amount: redeem_amount / 8 + } + .into())); + // Process rest of redemption at 50% rate + assert_ok!(pallet_investments::Pallet::::process_redeem_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::checked_from_rational(1, 2).unwrap(), + } + )); + // Order should have been cleared by fulfilling redemption assert_eq!( - >::convert(dot_location.clone()), - Some(DOT_ASSET_ID), + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), + ) + .amount, + 0 ); - assert_eq!( - >::convert(DOT_ASSET_ID), - Some(dot_location) - ) + // Collect remainder through Investments + assert_ok!(pallet_investments::Pallet::::collect_redemptions_for( + RawOrigin::Signed(Keyring::Alice.into()).into(), + investor.clone(), + default_investment_id::() + )); + assert!( + !pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) + ); + assert!(!frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemCollectedForNonClearedOrderId { + investment_id: default_investment_id::(), + who: investor.clone(), + } + .into() + })); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_investments::Event::::RedeemOrdersCollected { + investment_id: default_investment_id::(), + processed_orders: vec![1], + who: investor.clone(), + collection: RedeemCollection:: { + payout_investment_redeem: redeem_amount / 4, + remaining_investment_redeem: 0, + }, + outcome: CollectOutcome::FullyCollected, + } + .into() + })); + // Verify collected redemption was burned from investor + assert_eq!(orml_tokens::Pallet::::balance(currency_id, &investor), 0); + assert!(frame_system::Pallet::::events().iter().any(|e| e.event + == orml_tokens::Event::::Withdrawn { + currency_id, + who: investor.clone(), + amount: redeem_amount / 4 + } + .into())); + // Clearing of foreign RedeemState should have been dispatched exactly once + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + currency_payout: redeem_amount / 4, + tranche_tokens_payout: redeem_amount / 2, + remaining_redeem_amount: 0, + }, + } + .into() + })); }); } - #[test_runtimes([centrifuge])] - fn convert_unknown_multilocation() { - let mut env = FudgeEnv::::default(); + mod should_fail { + use super::*; - let unknown_location: Location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key([42].as_ref()), - ], - ); + mod decrease_should_underflow { + use super::*; - env.parachain_state_mut(|| { - assert!(>::convert(unknown_location).is_none()); - }); - } + #[test_runtimes([development])] + fn invest_decrease_underflow() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - #[test_runtimes([centrifuge])] - fn convert_unsupported_currency() { - let mut env = FudgeEnv::::default(); + setup_test(&mut env); - env.parachain_state_mut(|| { - assert_eq!( - >::convert(CurrencyId::Tranche( - 0, - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - )), - None - ) - }); - } - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let invest_amount: u128 = 10 * decimals(12); + let decrease_amount = invest_amount + 1; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id: CurrencyId = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_investment::( + pool_id, + invest_amount, + investor.clone(), + currency_id, + ); + enable_liquidity_pool_transferability::(currency_id); - mod restricted_transfers { - use cfg_types::tokens::{CurrencyId::Native, FilterCurrency}; + // Mock incoming decrease message + let msg = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: decrease_amount, + }; - use super::*; - use crate::generic::envs::runtime_env::RuntimeEnv; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + ), + pallet_foreign_investments::Error::::TooMuchDecrease + ); + }); + } - const TRANSFER_AMOUNT: u128 = 10; + #[test_runtimes([development])] + fn redeem_decrease_underflow() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - fn xcm_location() -> VersionedLocation { - VersionedLocation::V4(Location::new( - 1, - AccountId32 { - id: Keyring::Alice.into(), - network: None, - }, - )) - } + setup_test(&mut env); - fn allowed_xcm_location() -> RestrictedTransferLocation { - RestrictedTransferLocation::Xcm(Box::new(xcm_location())) - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let redeem_amount: u128 = 10 * decimals(12); + let decrease_amount = redeem_amount + 1; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id: CurrencyId = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_redemption::( + pool_id, + redeem_amount, + investor.clone(), + currency_id, + ); - fn add_allowance( - account: Keyring, - asset: CurrencyId, - location: RestrictedTransferLocation, - ) { - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(account.into()).into(), - FilterCurrency::Specific(asset), - location - ) - ); - } + // Mock incoming decrease message + let msg = LiquidityPoolMessage::DecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: decrease_amount, + }; - #[test_runtimes([centrifuge])] - fn restrict_cfg_extrinsic() { - let mut env = RuntimeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(TRANSFER_AMOUNT + 10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - USDC, - T::ExistentialDeposit::get() + usdc(TRANSFER_AMOUNT), - )], - }) - .storage(), - ); + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + ), + DispatchError::Arithmetic(sp_runtime::ArithmeticError::Underflow) + ); + }); + } + } - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = env - .parachain_state_mut(|| { - // NOTE: The para-id is not relevant here - register_cfg::(2031); + mod should_throw_requires_collect { + use super::*; - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(Keyring::Alice.into()).into(), - FilterCurrency::All, - RestrictedTransferLocation::Local(Keyring::Bob.id()) - ) + #[test_runtimes([development])] + fn invest_requires_collect() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), ); - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let call = pallet_balances::Call::::transfer_allow_death { - dest: Keyring::Charlie.into(), - value: cfg(TRANSFER_AMOUNT), - }; - env.submit_now(Keyring::Alice, call).unwrap(); - - let call = pallet_balances::Call::::transfer_allow_death { - dest: Keyring::Bob.into(), - value: cfg(TRANSFER_AMOUNT), - }; - let fee = env.submit_now(Keyring::Alice, call).unwrap(); - - // Restrict also CFG local - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!( - after_transfer_alice, - pre_transfer_alice - cfg(TRANSFER_AMOUNT) - 2 * fee - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + setup_test(&mut env); - #[test_runtimes([centrifuge])] - fn restrict_all() { - let mut env = RuntimeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(TRANSFER_AMOUNT + 10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - USDC, - T::ExistentialDeposit::get() + usdc(TRANSFER_AMOUNT), - )], - }) - .storage(), - ); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount: u128 = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id: CurrencyId = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_investment::( + pool_id, + amount, + investor.clone(), + currency_id, + ); + enable_liquidity_pool_transferability::(currency_id); - // Set allowance - env.parachain_state_mut(|| { - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(Keyring::Alice.into()).into(), - FilterCurrency::All, - RestrictedTransferLocation::Local(Keyring::Bob.id()) - ) - ); - }); + // Prepare collection + let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } + .into_account_truncating(); + assert_ok!(orml_tokens::Pallet::::mint_into( + currency_id, + &pool_account, + amount + )); + assert_ok!(pallet_investments::Pallet::::process_invest_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::invest_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::one(), + } + )); - // Restrict USDC local - env.parachain_state_mut(|| { - register_usdc::(); + // Should fail to increase + let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: AUSD_ED, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg + ), + pallet_investments::Error::::CollectRequired + ); - let pre_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let pre_transfer_bob = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.id()); - let pre_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); + // Should fail to decrease + let decrease_msg = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg + ), + pallet_investments::Error::::CollectRequired + ); + }); + } - assert_noop!( - pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Charlie.into(), - USDC, - lp_eth_usdc(TRANSFER_AMOUNT) - ), - pallet_restricted_tokens::Error::::PreConditionsNotMet - ); + #[test_runtimes([development])] + fn redeem_requires_collect() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); + setup_test(&mut env); - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount: u128 = 10 * decimals(12); + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let currency_id: CurrencyId = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); + do_initial_increase_redemption::( + pool_id, + amount, + investor.clone(), + currency_id, + ); + enable_liquidity_pool_transferability::(currency_id); - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Bob.into(), - USDC, - usdc(TRANSFER_AMOUNT) - ),); - - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let after_transfer_bob = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); + // Mint more into DomainLocator required for subsequent invest attempt + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), + 1, + )); - assert_eq!( - after_transfer_alice, - pre_transfer_alice - usdc(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + usdc(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); + // Prepare collection + let pool_account = pallet_pool_system::pool_types::PoolLocator { pool_id } + .into_account_truncating(); + assert_ok!(orml_tokens::Pallet::::mint_into( + currency_id, + &pool_account, + amount + )); + assert_ok!(pallet_investments::Pallet::::process_redeem_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::redeem_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::one(), + } + )); - // Restrict also CFG local - env.parachain_state_mut(|| { - // NOTE: The para-id is not relevant here - register_cfg::(2031); + // Should fail to increase + let increase_msg = LiquidityPoolMessage::IncreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg + ), + pallet_investments::Error::::CollectRequired + ); - let pre_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let pre_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let pre_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); + // Should fail to decrease + let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(currency_id), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg + ), + pallet_investments::Error::::CollectRequired + ); + }); + } + } - assert_noop!( - pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Charlie.into(), - Native, - cfg(TRANSFER_AMOUNT) - ), - pallet_restricted_tokens::Error::::PreConditionsNotMet - ); + mod payment_payout_currency { + use super::*; - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); + #[test_runtimes([development])] + fn invalid_invest_payment_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + setup_test(&mut env); - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Bob.into(), - Native, - cfg(TRANSFER_AMOUNT) - ),); - - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); + + create_currency_pool::(pool_id, pool_currency, currency_decimals.into()); + do_initial_increase_investment::( + pool_id, + amount, + investor.clone(), + pool_currency, + ); - assert_eq!( - after_transfer_alice, - pre_transfer_alice - cfg(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + enable_usdt_trading::(pool_currency, amount, true, true, true); - #[test_runtimes([centrifuge])] - fn restrict_lp_eth_usdc_transfer() { - let mut env = RuntimeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - LP_ETH_USDC, - T::ExistentialDeposit::get() + lp_eth_usdc(TRANSFER_AMOUNT), - )], - }) - .storage(), - ); + // Should fail to increase, decrease or collect for + // another foreign currency as long as + // `InvestmentState` exists + let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: AUSD_ED, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + let decrease_msg = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + let collect_msg = LiquidityPoolMessage::CollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + collect_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + }); + } - env.parachain_state_mut(|| { - register_lp_eth_usdc::(); - - let pre_transfer_alice = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Alice.id()); - let pre_transfer_bob = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Bob.id()); - let pre_transfer_charlie = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Charlie.id()); - - add_allowance::( - Keyring::Alice, - LP_ETH_USDC, - RestrictedTransferLocation::Local(Keyring::Bob.id()), - ); + #[test_runtimes([development])] + fn invalid_redeem_payout_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_noop!( - pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Charlie.into(), - LP_ETH_USDC, - lp_eth_usdc(TRANSFER_AMOUNT) - ), - pallet_restricted_tokens::Error::::PreConditionsNotMet - ); + setup_test(&mut env); + + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); + + create_currency_pool::(pool_id, pool_currency, currency_decimals.into()); + do_initial_increase_redemption::( + pool_id, + amount, + investor.clone(), + pool_currency, + ); + enable_usdt_trading::(pool_currency, amount, true, true, true); + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), + amount, + )); + + // Should fail to increase, decrease or collect for + // another foreign currency as long as + // `RedemptionState` exists + let increase_msg = LiquidityPoolMessage::IncreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + let collect_msg = LiquidityPoolMessage::CollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + collect_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + }); + } - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Alice.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Charlie.id()); + #[test_runtimes([development])] + fn redeem_payout_currency_not_found() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + setup_test(&mut env); - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Bob.into(), - LP_ETH_USDC, - lp_eth_usdc(TRANSFER_AMOUNT) - ),); - - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Alice.id()); - let after_transfer_bob = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Bob.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(LP_ETH_USDC, &Keyring::Charlie.id()); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); + + create_currency_pool::(pool_id, pool_currency, currency_decimals.into()); + do_initial_increase_redemption::( + pool_id, + amount, + investor.clone(), + pool_currency, + ); + enable_usdt_trading::(pool_currency, amount, true, true, true); + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()), + amount, + )); + + // Should fail to decrease or collect for another + // foreign currency as long as `RedemptionState` + // exists + let decrease_msg = LiquidityPoolMessage::DecreaseRedeemOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: 1, + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); - assert_eq!( - after_transfer_alice, - pre_transfer_alice - lp_eth_usdc(TRANSFER_AMOUNT) - ); - assert_eq!( - after_transfer_bob, - pre_transfer_bob + lp_eth_usdc(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); + let collect_msg = LiquidityPoolMessage::CollectRedeem { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + }; + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + collect_msg + ), + pallet_foreign_investments::Error::::MismatchedForeignCurrency + ); + }); + } + } } + } + + mod mismatching_currencies { + use super::*; - #[test_runtimes([centrifuge])] - fn restrict_lp_eth_usdc_lp_transfer() { + #[test_runtimes([development])] + fn collect_foreign_investment_for() { let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(cfg(10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - LP_ETH_USDC, - T::ExistentialDeposit::get() + lp_eth_usdc(TRANSFER_AMOUNT), - )], - }) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - setup_xcm(&mut env); + setup_test(&mut env); env.parachain_state_mut(|| { - register_usdc::(); - register_lp_eth_usdc::(); - - assert_ok!(orml_tokens::Pallet::::set_balance( - ::RuntimeOrigin::root(), - ::Sender::get().into(), - USDC, - usdc(1_000), - 0, - )); - - let router = DomainRouter::EthereumXCM(EthereumXCMRouter:: { - router: XCMRouter { - xcm_domain: XcmDomain { - location: Box::new( - Location::new(1, Parachain(T::FudgeHandle::SIBLING_ID)).into(), - ), - ethereum_xcm_transact_call_index: BoundedVec::truncate_from(vec![ - 38, 0, - ]), - contract_address: H160::from_low_u64_be(11), - max_gas_limit: 700_000, - transact_required_weight_at_most: Default::default(), - overall_weight: Default::default(), - fee_currency: USDC, - fee_amount: usdc(1), - }, - _marker: Default::default(), - }, - _marker: Default::default(), - }); - - assert_ok!( - pallet_liquidity_pools_gateway::Pallet::::set_domain_router( - ::RuntimeOrigin::root(), - Domain::EVM(1), - router, - ) - ); - - let receiver = H160::from_slice( - &>::as_ref(&Keyring::Charlie.id()) - [0..20], + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency: CurrencyId = AUSD_CURRENCY_ID; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let pool_currency_decimals = currency_decimals::AUSD; + let invest_amount_pool_denominated: u128 = 6 * decimals(18); + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); + let trader: AccountId = Keyring::Alice.into(); + create_currency_pool::(pool_id, pool_currency, pool_currency_decimals.into()); + + // USDT investment preparations + let invest_amount_foreign_denominated = enable_usdt_trading::( + pool_currency, + invest_amount_pool_denominated, + true, + true, + // not needed because we don't initialize a swap from pool to foreign here + false, ); - let domain_address = DomainAddress::EVM(1, receiver.into()); - - add_allowance::( - Keyring::Alice, - LP_ETH_USDC, - RestrictedTransferLocation::Address(domain_address.clone()), + // Do first investment and fulfill swap order + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, ); - - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - LP_ETH_USDC, - DomainAddress::EVM(1, [1u8; 20]), - lp_eth_usdc(TRANSFER_AMOUNT), - ), - pallet_transfer_allowlist::Error::::NoAllowanceForDestination + fulfill_swap_into_pool::( + pool_id, + default_order_id::(&investor), + invest_amount_pool_denominated, + invest_amount_foreign_denominated, + trader, ); - let total_issuance_pre = orml_tokens::Pallet::::total_issuance(LP_ETH_USDC); + // Increase invest order to initialize ForeignInvestmentInfo + let msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - assert_ok!(pallet_liquidity_pools::Pallet::::transfer( + // Process 100% of investment at 50% rate (1 pool currency = 2 tranche tokens) + assert_ok!(pallet_investments::Pallet::::process_invest_orders( + default_investment_id::() + )); + assert_ok!(pallet_investments::Pallet::::invest_fulfillment( + default_investment_id::(), + FulfillmentWithPrice { + of_amount: Perquintill::one(), + price: Ratio::checked_from_rational(1, 2).unwrap(), + } + )); + assert_ok!(pallet_investments::Pallet::::collect_investments_for( RawOrigin::Signed(Keyring::Alice.into()).into(), - LP_ETH_USDC, - domain_address, - lp_eth_usdc(TRANSFER_AMOUNT), + investor.clone(), + default_investment_id::() )); - + assert!(orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &investor + ) + .is_zero()); assert_eq!( - orml_tokens::Pallet::::total_issuance(LP_ETH_USDC), - total_issuance_pre - lp_eth_usdc(TRANSFER_AMOUNT), - ); - }); - } - - #[test_runtimes([centrifuge])] - fn restrict_usdc_transfer() { - let mut env = RuntimeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - USDC, - T::ExistentialDeposit::get() + usdc(TRANSFER_AMOUNT), - )], - }) - .storage(), - ); - - env.parachain_state_mut(|| { - register_usdc::(); - - let pre_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let pre_transfer_bob = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.id()); - let pre_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); - - add_allowance::( - Keyring::Alice, - USDC, - RestrictedTransferLocation::Local(Keyring::Bob.id()), - ); - - assert_noop!( - pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Charlie.into(), - USDC, - lp_eth_usdc(TRANSFER_AMOUNT) + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator ), - pallet_restricted_tokens::Error::::PreConditionsNotMet + invest_amount_pool_denominated * 2 ); - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); - - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Bob.into(), - USDC, - usdc(TRANSFER_AMOUNT) - ),); - - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.id()); - let after_transfer_bob = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Bob.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Charlie.id()); + let sender = ::Sender::get(); - assert_eq!( - after_transfer_alice, - pre_transfer_alice - usdc(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + usdc(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedCollectInvest { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + currency_payout: invest_amount_foreign_denominated, + tranche_tokens_payout: 2 * invest_amount_pool_denominated, + remaining_invest_amount: invest_amount_foreign_denominated, + }, + } + .into() + })); }); } - #[test_runtimes([centrifuge])] - fn restrict_usdc_xcm_transfer() { - let mut env = FudgeEnv::::from_storage( - paras::GenesisConfig::> { - _config: Default::default(), - paras: vec![( - 1000.into(), - ParaGenesisArgs { - genesis_head: Default::default(), - validation_code: ValidationCode::from(vec![0, 1, 2, 3]), - para_kind: ParaKind::Parachain, - }, - )], - } - .build_storage() - .unwrap(), + /// Invest in pool currency, then increase in allowed foreign + /// currency, then decrease in same foreign currency multiple times. + #[test_runtimes([development])] + fn increase_fulfill_increase_decrease_decrease_partial() { + let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(cfg(10))) + .add(genesis::balances::(cfg(1_000))) .storage(), - Default::default(), ); - // Configuring XCM in this test fails because the Hrmp - // configuration is not applied. We force the application here, - // but we should configure correctly this because something is off. - env.relay_state_mut(|| { - polkadot_runtime_parachains::configuration::Pallet::>::force_set_active_config( - crate::generic::envs::fudge_env::handle::hrmp_host_config() - ); - }); - - setup_xcm(&mut env); - - setup_usdc_xcm(&mut env); - - env.sibling_state_mut(|| { - register_usdc::(); - }); + setup_test(&mut env); env.parachain_state_mut(|| { - register_usdc::(); + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency: CurrencyId = AUSD_CURRENCY_ID; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let pool_currency_decimals = currency_decimals::AUSD; + let invest_amount_pool_denominated: u128 = 6 * decimals(18); + let trader: AccountId = Keyring::Alice.into(); + create_currency_pool::(pool_id, pool_currency, pool_currency_decimals.into()); + + // USDT investment preparations + let invest_amount_foreign_denominated = enable_usdt_trading::( + pool_currency, + invest_amount_pool_denominated, + true, + true, + true, + ); - let alice_initial_usdc = usdc(3_000); + // Do first investment and fulfill swap order + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, + ); + fulfill_swap_into_pool::( + pool_id, + default_order_id::(&investor), + invest_amount_pool_denominated, + invest_amount_foreign_denominated, + trader.clone(), + ); - assert_ok!(orml_tokens::Pallet::::mint_into( - USDC, - &Keyring::Alice.into(), - alice_initial_usdc + // Do second investment and not fulfill swap order + let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg )); - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(Keyring::Alice.into()).into(), - FilterCurrency::Specific(USDC), - RestrictedTransferLocation::Xcm(Box::new(VersionedLocation::V4( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - id: Keyring::Alice.into(), - network: None, - } - ] - ) - ))) - ) - ); + // Decrease pending pool swap by same amount + let decrease_msg_pool_swap_amount = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg_pool_swap_amount + )); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: ::Sender::get(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + currency_payout: invest_amount_foreign_denominated, + remaining_invest_amount: invest_amount_foreign_denominated, + }, + } + .into() + })); - assert_noop!( - pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - USDC, - usdc(1_000), - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - id: Keyring::Bob.into(), - network: None, - } - ] - ) - .into() - ), - WeightLimit::Unlimited, - ), - pallet_transfer_allowlist::Error::::NoAllowanceForDestination - ); + // Decrease partially investing amount + let decrease_msg_partial_invest_amount = + LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated / 2, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg_partial_invest_amount.clone() + )); - assert_ok!(pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - USDC, - usdc(1_000), - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - id: Keyring::Alice.into(), - network: None, - } - ] - ) + // Consume entire investing amount by sending same message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg_partial_invest_amount.clone() + )); + + // Swap decreased amount + assert_ok!(pallet_order_book::Pallet::::fill_order( + RawOrigin::Signed(trader.clone()).into(), + default_order_id::(&investor), + invest_amount_pool_denominated + )); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: ::Sender::get(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + currency_payout: invest_amount_foreign_denominated, + remaining_invest_amount: 0, + }, + } .into() - ), - WeightLimit::Unlimited, - )); - - assert_eq!( - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.into()), - alice_initial_usdc - usdc(1_000), - ); + })); }); - - // NOTE - we cannot confirm that the Alice account present on the - // sibling receives this transfer since the orml_xtokens pallet - // sends a message to parachain 1000 (the parachain of the USDC - // currency) which in turn should send a message to the sibling. - // Since parachain 1000 is just a dummy added in the paras - // genesis config and not an actual sibling with a runtime, the - // transfer does not take place. } - #[test_runtimes([centrifuge])] - fn restrict_dot_transfer() { - let mut env = RuntimeEnv::::from_parachain_storage( + /// Propagate swaps only via OrderBook fulfillments. + /// + /// Flow: Increase, fulfill, decrease, fulfill + #[test_runtimes([development])] + fn invest_swaps_happy_path() { + let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(cfg(10))) - .add(orml_tokens::GenesisConfig:: { - balances: vec![( - Keyring::Alice.id(), - DOT_ASSET_ID, - T::ExistentialDeposit::get() + dot(TRANSFER_AMOUNT), - )], - }) + .add(genesis::balances::(cfg(1_000))) + .add(genesis::tokens::(vec![ + (AUSD_CURRENCY_ID, AUSD_ED), + (USDT_CURRENCY_ID, USDT_ED), + ])) .storage(), ); + setup_test(&mut env); + env.parachain_state_mut(|| { - register_dot::(); - - let pre_transfer_alice = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.id()); - let pre_transfer_bob = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Bob.id()); - let pre_transfer_charlie = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Charlie.id()); - - add_allowance::( - Keyring::Alice, - DOT_ASSET_ID, - RestrictedTransferLocation::Local(Keyring::Bob.id()), + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let trader: AccountId = Keyring::Alice.into(); + let pool_currency: CurrencyId = AUSD_CURRENCY_ID; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let pool_currency_decimals = currency_decimals::AUSD; + let invest_amount_pool_denominated: u128 = 10 * decimals(18); + create_currency_pool::(pool_id, pool_currency, pool_currency_decimals.into()); + let invest_amount_foreign_denominated: u128 = enable_usdt_trading::( + pool_currency, + invest_amount_pool_denominated, + true, + true, + true, ); - assert_noop!( - pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Charlie.into(), - DOT_ASSET_ID, - dot(TRANSFER_AMOUNT) - ), - pallet_restricted_tokens::Error::::PreConditionsNotMet + // Increase such that active swap into USDT is initialized + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, + ); + + // Fulfilling order should propagate it from swapping to investing + let swap_order_id = default_order_id::(&investor); + fulfill_swap_into_pool::( + pool_id, + swap_order_id, + invest_amount_pool_denominated, + invest_amount_foreign_denominated, + trader.clone(), ); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_order_book::Event::::OrderFulfillment { + order_id: swap_order_id, + placing_account: investor.clone(), + fulfilling_account: trader.clone(), + partial_fulfillment: false, + fulfillment_amount: invest_amount_foreign_denominated, + currency_in: pool_currency, + currency_out: foreign_currency, + ratio: Ratio::one(), + } + .into() + })); - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Charlie.id()); + // Decrease by half the investment amount + let msg = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated / 2, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg.clone() + )); - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + let swap_order_id = default_order_id::(&investor); + assert_ok!(pallet_order_book::Pallet::::fill_order( + RawOrigin::Signed(trader.clone()).into(), + swap_order_id, + invest_amount_pool_denominated / 2 + )); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_order_book::Event::::OrderFulfillment { + order_id: swap_order_id, + placing_account: investor.clone(), + fulfilling_account: trader.clone(), + partial_fulfillment: false, + fulfillment_amount: invest_amount_pool_denominated / 2, + currency_in: foreign_currency, + currency_out: pool_currency, + ratio: Ratio::one(), + } + .into() + })); - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Keyring::Bob.into(), - DOT_ASSET_ID, - dot(TRANSFER_AMOUNT) - ),); - - let after_transfer_alice = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.id()); - let after_transfer_bob = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Bob.id()); - let after_transfer_charlie = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Charlie.id()); + let sender = ::Sender::get(); - assert_eq!( - after_transfer_alice, - pre_transfer_alice - dot(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + dot(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: sender.clone(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + currency_payout: invest_amount_foreign_denominated / 2, + remaining_invest_amount: invest_amount_foreign_denominated / 2, + }, + } + .into() + })); }); } - #[test_runtimes([centrifuge])] - fn restrict_dot_xcm_transfer() { + #[test_runtimes([development])] + fn increase_fulfill_decrease_fulfill_partial_increase() { let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(cfg(10))) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - transfer_dot_from_relay_chain(&mut env); + setup_test(&mut env); env.parachain_state_mut(|| { - let alice_initial_dot = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()); - - assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new(1, Junctions::Here)), - XCM_VERSION, - )); - - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(Keyring::Alice.into()).into(), - FilterCurrency::Specific(DOT_ASSET_ID), - allowed_xcm_location() - ) + let pool_id = POOL_ID; + let investor: AccountId = + AccountConverter::convert((DOMAIN_MOONBEAM, Keyring::Bob.into())); + let pool_currency: CurrencyId = AUSD_CURRENCY_ID; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let pool_currency_decimals = currency_decimals::AUSD; + let invest_amount_pool_denominated: u128 = 10 * decimals(18); + let trader: AccountId = Keyring::Alice.into(); + create_currency_pool::(pool_id, pool_currency, pool_currency_decimals.into()); + + // USDT investment preparations + let invest_amount_foreign_denominated = enable_usdt_trading::( + pool_currency, + invest_amount_pool_denominated, + true, + true, + true, ); - assert_noop!( - pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - DOT_ASSET_ID, - dot(1), - Box::new( - Location::new( - 1, - Junction::AccountId32 { - id: Keyring::Bob.into(), - network: None, - } - ) - .into() - ), - WeightLimit::Unlimited, - ), - pallet_transfer_allowlist::Error::::NoAllowanceForDestination + // Do first investment and fulfill swap order + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, + ); + fulfill_swap_into_pool::( + pool_id, + default_order_id::(&investor), + invest_amount_pool_denominated, + invest_amount_foreign_denominated, + trader.clone(), ); - assert_ok!(pallet_restricted_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - DOT_ASSET_ID, - dot(1), - Box::new( - Location::new( - 1, - Junction::AccountId32 { - id: Keyring::Alice.into(), - network: None, - } - ) - .into() - ), - WeightLimit::Unlimited, + // Decrease pending pool swap by same amount + let decrease_msg_pool_swap_amount = LiquidityPoolMessage::DecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + decrease_msg_pool_swap_amount )); - assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - alice_initial_dot - dot(1), - ); - }); + // Fulfill decrease swap partially + assert_ok!(pallet_order_book::Pallet::::fill_order( + RawOrigin::Signed(trader.clone()).into(), + default_order_id::(&investor), + 3 * invest_amount_pool_denominated / 4 + )); - env.pass(Blocks::ByNumber(2)); + // Increase more than pending swap (pool -> foreign) amount from decrease + let increase_msg = LiquidityPoolMessage::IncreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + amount: invest_amount_foreign_denominated / 2, + }; + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + increase_msg + )); - env.relay_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() - ), - 79857365914 - ); + assert!(frame_system::Pallet::::events().iter().any(|e| { + e.event + == pallet_liquidity_pools_gateway::Event::::OutboundMessageSubmitted { + sender: ::Sender::get(), + domain: DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain(), + message: LiquidityPoolMessage::ExecutedDecreaseInvestOrder { + pool_id, + tranche_id: default_tranche_id::(pool_id), + investor: investor.clone().into(), + currency: general_currency_index::(foreign_currency), + currency_payout: invest_amount_foreign_denominated, + remaining_invest_amount: invest_amount_foreign_denominated / 2, + }, + } + .into() + })); }); } } +} - mod transfers { - use super::*; +mod transfers { + use super::*; - fn transfer_cfg_to_sibling(env: &mut FudgeEnv) { - let alice_initial_balance = cfg(10); - let transfer_amount = cfg(5); - let cfg_in_sibling = CurrencyId::ForeignAsset(12); - - // CFG Metadata - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key(parachains::polkadot::centrifuge::CFG_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + // TODO: Must be moved to lp/transfers.rs (?) or to UT? It seems more an UT. - env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - 0 - ); + #[test_runtimes([development])] + fn transfer_non_tranche_tokens_from_local() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(CurrencyId::Native), - )); - }); + setup_test(&mut env); - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - 0 - ); + env.parachain_state_mut(|| { + let initial_balance = 2 * AUSD_ED; + let amount = initial_balance / 2; + let dest_address = DEFAULT_DOMAIN_ADDRESS_MOONBEAM; + let currency_id = AUSD_CURRENCY_ID; + let source_account = Keyring::Charlie; - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(cfg_in_sibling) - )); - }); + // Mint sufficient balance + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), + 0 + ); + assert_ok!(orml_tokens::Pallet::::mint_into( + currency_id, + &source_account.into(), + initial_balance + )); + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), + initial_balance + ); - env.parachain_state_mut(|| { - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), + // Only `ForeignAsset` can be transferred + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + CurrencyId::Tranche(42u64, [0u8; 16]), + dest_address.clone(), + amount, + ), + pallet_liquidity_pools::Error::::InvalidTransferCurrency + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards), + dest_address.clone(), + amount, + ), + pallet_liquidity_pools::Error::::AssetNotFound + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), CurrencyId::Native, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + dest_address.clone(), + amount, + ), + pallet_liquidity_pools::Error::::AssetNotFound + ); - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - transfer_amount - ); + // Cannot transfer as long as cross chain transferability is disabled + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + currency_id, + dest_address.clone(), + initial_balance, + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable + ); - // Verify that the amount transferred is now part of the sibling account here - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::SIBLING_ID - )), - transfer_amount - ); - }); + // Enable LiquidityPools transferability + enable_liquidity_pool_transferability::(currency_id); + + // Cannot transfer more than owned + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + currency_id, + dest_address.clone(), + initial_balance.saturating_add(1), + ), + pallet_liquidity_pools::Error::::BalanceTooLow + ); - env.pass(Blocks::ByNumber(2)); + let pre_total_issuance = orml_tokens::Pallet::::total_issuance(currency_id); + + assert_ok!(pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + currency_id, + dest_address.clone(), + amount, + )); - env.sibling_state_mut(|| { - let current_balance = - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()); + assert_eq!( + orml_tokens::Pallet::::total_issuance(currency_id), + pre_total_issuance - amount + ); + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), + initial_balance - amount + ); + }); + } +} - // Verify that Keyring::Bob now has (amount transferred - fee) - assert_eq!(current_balance, transfer_amount - fee(18)); +mod routers { + use super::*; - // Sanity check for the actual amount Keyring::Bob ends up with - assert_eq!(current_balance, 4993570400000000000); - }); - } + mod axelar_evm { + use std::ops::AddAssign; - #[test_runtimes([centrifuge])] - fn test_cfg_transfers_to_and_from_sibling() { + use super::*; + + #[test_runtimes([development])] + fn test_via_outbound_queue() { let mut env = FudgeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::(cfg(10))) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - setup_xcm(&mut env); - - // In order to be able to transfer CFG from Sibling to Centrifuge, we need to - // first send CFG from Centrifuge to Sibling, or else it fails since it'd be - // like Sibling had minted CFG on their side. - transfer_cfg_to_sibling(&mut env); + let test_domain = Domain::EVM(1); - let alice_initial_balance = cfg(5); - let bob_initial_balance = cfg(5) - cfg_fee(); - let transfer_amount = cfg(1); - // Note: This asset was registered in `transfer_cfg_to_sibling` - let cfg_in_sibling = CurrencyId::ForeignAsset(12); + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code: Vec = vec![0, 0, 0]; + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let liquidity_pools_contract_address = H160::from_low_u64_be(2); env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance - ); + pallet_evm::AccountCodes::::insert(axelar_contract_address, axelar_contract_code) }); - env.sibling_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::PARA_ID - )), - 0 - ); - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - bob_initial_balance - ); - }); + let transaction_call_cost = + env.parachain_state(|| ::config().gas_transaction_call); - env.sibling_state_mut(|| { - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Bob.into()).into(), - cfg_in_sibling, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Alice.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + let evm_domain = EVMDomain { + target_contract_address: axelar_contract_address, + target_contract_hash: axelar_contract_hash, + fee_values: FeeValues { + value: U256::from(0), + gas_limit: U256::from(transaction_call_cost + 1_000_000), + gas_price: U256::from(10), + }, + }; - // Confirm that Bobs's balance is initial balance - amount transferred - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - bob_initial_balance - transfer_amount - ); - }); + let axelar_evm_router = AxelarEVMRouter:: { + router: EVMRouter { + evm_domain, + _marker: Default::default(), + }, + evm_chain: BoundedVec::>::try_from( + "ethereum".as_bytes().to_vec(), + ) + .unwrap(), + _marker: Default::default(), + liquidity_pools_contract_address, + }; - env.pass(Blocks::ByNumber(3)); + let test_router = DomainRouter::::AxelarEVM(axelar_evm_router); env.parachain_state_mut(|| { - // Verify that Keyring::Alice now has initial balance + amount transferred - fee - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance + transfer_amount - cfg_fee(), + assert_ok!( + pallet_liquidity_pools_gateway::Pallet::::set_domain_router( + ::RuntimeOrigin::root(), + test_domain.clone(), + test_router, + ) ); }); - } - #[test_runtimes([centrifuge])] - fn transfer_ausd_to_centrifuge() { - let mut env = FudgeEnv::::default(); + let sender = Keyring::Alice.id(); + let gateway_sender = env + .parachain_state(|| ::Sender::get()); - setup_xcm(&mut env); + let gateway_sender_h160: H160 = H160::from_slice( + &>::as_ref(&gateway_sender)[0..20], + ); - let alice_initial_balance = ausd(10); - let transfer_amount = ausd(7); + let msg = LiquidityPoolMessage::Transfer { + currency: 0, + sender: Keyring::Alice.id().into(), + receiver: Keyring::Bob.id().into(), + amount: 1_000u128, + }; - env.sibling_state_mut(|| { - register_ausd::(); + // Failure - gateway sender account is not funded. + assert_ok!(env.parachain_state_mut(|| { + as OutboundQueue>::submit( + sender.clone(), + test_domain.clone(), + msg.clone(), + ) + })); - assert_ok!(orml_tokens::Pallet::::deposit( - AUSD_CURRENCY_ID, - &Keyring::Alice.into(), - alice_initial_balance - )); + let mut nonce = T::OutboundMessageNonce::one(); - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - 0 - ); + let expected_event = + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionFailure { + sender: gateway_sender.clone(), + domain: test_domain.clone(), + message: msg.clone(), + error: pallet_evm::Error::::BalanceLow.into(), + nonce, + }; + + env.pass(Blocks::UntilEvent { + event: expected_event.clone().into(), + limit: 3, }); - env.parachain_state_mut(|| { - register_ausd::(); + env.check_event(expected_event) + .expect("expected RouterExecutionFailure event"); - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - 0, - ); - }); + nonce.add_assign(T::OutboundMessageNonce::one()); - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - &Keyring::Alice.into() - ), - ausd(10), + assert_ok!(env.parachain_state_mut(|| { + // Note how both the target address and the gateway sender need to have some + // balance. + crate::generic::utils::evm::mint_balance_into_derived_account::( + axelar_contract_address, + cfg(1_000_000_000), ); - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - AUSD_CURRENCY_ID, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); - - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - &Keyring::Alice.into() - ), - alice_initial_balance - transfer_amount + crate::generic::utils::evm::mint_balance_into_derived_account::( + gateway_sender_h160, + cfg(1_000_000), ); - // Verify that the amount transferred is now part of the centrifuge parachain - // account here - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - transfer_amount - ); - }); + as OutboundQueue>::submit( + sender.clone(), + test_domain.clone(), + msg.clone(), + ) + })); - env.pass(Blocks::ByNumber(3)); + let expected_event = + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { + sender: gateway_sender.clone(), + domain: test_domain.clone(), + message: msg.clone(), + nonce, + }; - env.parachain_state_mut(|| { - // Verify that Keyring::Bob now has initial balance + amount transferred - fee - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - transfer_amount - ausd_fee() - ); + env.pass(Blocks::UntilEvent { + event: expected_event.clone().into(), + limit: 3, }); - } - #[test_runtimes([centrifuge])] - fn transfer_dot_to_and_from_relay_chain() { - let mut env = FudgeEnv::::default(); + env.check_event(expected_event) + .expect("expected OutboundMessageExecutionSuccess event"); - transfer_dot_from_relay_chain(&mut env); + // Router not found + let unused_domain = Domain::EVM(1234); env.parachain_state_mut(|| { - let alice_initial_dot = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()); - - assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new(1, Junctions::Here)), - XCM_VERSION, - )); - - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - DOT_ASSET_ID, - dot(1), - Box::new( - Location::new( - 1, - Junction::AccountId32 { - id: Keyring::Alice.into(), - network: None, - } - ) - .into() + assert_noop!( + as OutboundQueue>::submit( + sender, + unused_domain.clone(), + msg, ), - WeightLimit::Unlimited, - )); - - assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - alice_initial_dot - dot(1), + pallet_liquidity_pools_gateway::Error::::RouterNotFound ); }); + } + } - env.pass(Blocks::ByNumber(2)); + mod ethereum_xcm { + use super::*; - env.relay_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() - ), - 79857365914 - ); - }); - } + mod utils { + use super::*; - #[test_runtimes([centrifuge])] - fn transfer_foreign_sibling_to_centrifuge() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(10))) - .storage(), - ); + pub fn submit_test_fn( + router_creation_fn: RouterCreationFn, + ) { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - setup_xcm(&mut env); + setup_test(&mut env); - let sibling_asset_id = CurrencyId::ForeignAsset(1); - let asset_location = Location::new( - 1, - [Parachain(T::FudgeHandle::SIBLING_ID), general_key(&[0, 1])], - ); - let meta: AssetMetadata = AssetMetadata { - decimals: 18, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1_000_000_000_000, - location: Some(VersionedLocation::V4(asset_location)), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(XcmMetadata { - // We specify a custom fee_per_second and verify below that this value is - // used when XCM transfer fees are charged for this token. - fee_per_second: Some(8420000000000000000), - }), - ..CustomMetadata::default() - }, - }; - let transfer_amount = foreign(1, meta.decimals); + enable_para_to_sibling_communication::(&mut env); - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()), - 0 - ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(CurrencyId::Native), - )); - }); + let msg = Message::::Transfer { + currency: 0, + sender: Keyring::Alice.into(), + receiver: Keyring::Bob.into(), + amount: 1_000u128, + }; - env.parachain_state_mut(|| { - // First, register the asset in centrifuge - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(sibling_asset_id) - )); - }); + env.parachain_state_mut(|| { + let domain_router = router_creation_fn( + Location::new(1, Parachain(SIBLING_ID)).into(), + GLMR_CURRENCY_ID, + ); - env.sibling_state_mut(|| { - assert_ok!(pallet_balances::Pallet::::force_set_balance( - ::RuntimeOrigin::root(), - Keyring::Alice.id().into(), - transfer_amount * 2, - )); + assert_ok!( + pallet_liquidity_pools_gateway::Pallet::::set_domain_router( + ::RuntimeOrigin::root(), + TEST_DOMAIN, + domain_router, + ) + ); - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - CurrencyId::Native, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] + assert_ok!( + as OutboundQueue>::submit( + Keyring::Alice.into(), + TEST_DOMAIN, + msg.clone(), ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + ); + }); - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - transfer_amount - ); - }); + let gateway_sender = env.parachain_state(|| { + ::Sender::get() + }); - env.pass(Blocks::ByNumber(3)); + let expected_event = + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { + sender: gateway_sender, + domain: TEST_DOMAIN, + message: msg, + nonce: T::OutboundMessageNonce::one(), + }; - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()); + env.pass(Blocks::UntilEvent { + event: expected_event.clone().into(), + limit: 3, + }); - // Verify that Keyring::Bob now has initial balance + amount transferred - fee - assert_eq!( - bob_balance, - transfer_amount - - calc_fee( - xcm_metadata(meta.additional.transferability) - .unwrap() - .fee_per_second - .unwrap() - ) - ); - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 993264000000000000); - }); - } + env.check_event(expected_event) + .expect("expected OutboundMessageExecutionSuccess event"); + } - #[test_runtimes([centrifuge])] - fn transfer_wormhole_usdc_acala_to_centrifuge() { - let mut env = FudgeEnv::::from_storage( - Default::default(), - Default::default(), - Genesis::default() - .add(genesis::balances::(cfg(10))) - .storage(), - ); + type RouterCreationFn = + Box DomainRouter>; + + pub fn get_axelar_xcm_router_fn() -> RouterCreationFn { + Box::new( + |location: VersionedLocation, currency_id: CurrencyId| -> DomainRouter { + let router = AxelarXCMRouter:: { + router: XCMRouter { + xcm_domain: XcmDomain { + location: Box::new( + location.try_into().expect("Bad xcm domain location"), + ), + ethereum_xcm_transact_call_index: BoundedVec::truncate_from( + vec![38, 0], + ), + contract_address: H160::from_low_u64_be(11), + max_gas_limit: 700_000, + transact_required_weight_at_most: Default::default(), + overall_weight: Default::default(), + fee_currency: currency_id, + fee_amount: decimals(18).saturating_div(5), + }, + _marker: Default::default(), + }, + axelar_target_chain: BoundedVec::< + u8, + ConstU32, + >::try_from("ethereum".as_bytes().to_vec()) + .unwrap(), + axelar_target_contract: H160::from_low_u64_be(111), + _marker: Default::default(), + }; - setup_xcm(&mut env); + DomainRouter::AxelarXCM(router) + }, + ) + } - let usdc_asset_id = CurrencyId::ForeignAsset(39); - let asset_location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - general_key("0x02f3a00dd12f644daec907013b16eb6d14bf1c4cb4".as_bytes()), - ], - ); - let meta: AssetMetadata = AssetMetadata { - decimals: 6, - name: BoundedVec::default(), - symbol: BoundedVec::default(), - existential_deposit: 1, - location: Some(VersionedLocation::V4(asset_location)), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; - let transfer_amount = foreign(12, meta.decimals); - let alice_initial_balance = transfer_amount * 100; - - env.sibling_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(usdc_asset_id) - )); - assert_ok!(orml_tokens::Pallet::::deposit( - usdc_asset_id, - &Keyring::Alice.into(), - alice_initial_balance - )); - assert_eq!( - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Alice.into()), - alice_initial_balance - ); - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - cfg(10) - ); - }); + pub fn get_ethereum_xcm_router_fn() -> RouterCreationFn { + Box::new( + |location: VersionedLocation, currency_id: CurrencyId| -> DomainRouter { + let router = EthereumXCMRouter:: { + router: XCMRouter { + xcm_domain: XcmDomain { + location: Box::new( + location.try_into().expect("Bad xcm domain location"), + ), + ethereum_xcm_transact_call_index: BoundedVec::truncate_from( + vec![38, 0], + ), + contract_address: H160::from_low_u64_be(11), + max_gas_limit: 700_000, + transact_required_weight_at_most: Default::default(), + overall_weight: Default::default(), + fee_currency: currency_id, + fee_amount: decimals(18).saturating_div(5), + }, + _marker: Default::default(), + }, + _marker: Default::default(), + }; - env.parachain_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(usdc_asset_id) - )); - }); + DomainRouter::EthereumXCM(router) + }, + ) + } + } - env.sibling_state_mut(|| { - assert_ok!(orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - usdc_asset_id, - transfer_amount, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000.into()), - )); - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Alice.into()), - alice_initial_balance - transfer_amount - ); - }); + use utils::*; - env.pass(Blocks::ByNumber(3)); + const TEST_DOMAIN: Domain = Domain::EVM(1); - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Bob.into()); + #[test_runtimes([development])] + fn submit_ethereum_xcm() { + submit_test_fn::(get_ethereum_xcm_router_fn::()); + } - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 11993571); - }); + #[test_runtimes([development])] + fn submit_axelar_xcm() { + submit_test_fn::(get_axelar_xcm_router_fn::()); } } } -mod all { +mod gateway { use super::*; - mod restricted_calls { - use super::*; + #[test_runtimes([development])] + fn set_domain_router() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::council_members::(get_council_members())) + .storage(), + ); - #[test_runtimes(all)] - fn xtokens_transfer() { - let mut env = FudgeEnv::::default(); + let test_domain = Domain::EVM(1); - env.parachain_state_mut(|| { - assert_noop!( - orml_xtokens::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - CurrencyId::Tranche(401, [0; 16]), - 42, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - ), - orml_xtokens::Error::::NotCrossChainTransferableCurrency - ); - }); - } + let axelar_contract_address = H160::from_low_u64_be(1); + let axelar_contract_code: Vec = vec![0, 0, 0]; + let axelar_contract_hash = BlakeTwo256::hash_of(&axelar_contract_code); + let liquidity_pools_contract_address = H160::from_low_u64_be(2); - #[test_runtimes(all)] - fn xtokens_transfer_multiasset() { - let mut env = FudgeEnv::::default(); + env.parachain_state_mut(|| { + pallet_evm::AccountCodes::::insert(axelar_contract_address, axelar_contract_code) + }); - let tranche_currency = CurrencyId::Tranche(401, [0; 16]); - let tranche_id = - WeakBoundedVec::>::force_from(tranche_currency.encode(), None); - let tranche_location = Location::new( - 1, - [ - Parachain(123), - PalletInstance(42), - GeneralKey { - length: tranche_id.len() as u8, - data: vec_to_fixed_array(tranche_id), - }, - ], - ); - let tranche_asset = VersionedAsset::from(Asset::from(( - AssetId(tranche_location), - Fungibility::Fungible(42), - ))); + let evm_domain = EVMDomain { + target_contract_address: axelar_contract_address, + target_contract_hash: axelar_contract_hash, + fee_values: FeeValues { + value: U256::from(10), + gas_limit: U256::from(1_000_000), + gas_price: U256::from(10), + }, + }; - env.parachain_state_mut(|| { - assert_noop!( - orml_xtokens::Pallet::::transfer_multiasset( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Box::new(tranche_asset), - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - ), - orml_xtokens::Error::::XcmExecutionFailed - ); - }); - } + let axelar_evm_router = AxelarEVMRouter:: { + router: EVMRouter { + evm_domain, + _marker: Default::default(), + }, + evm_chain: BoundedVec::>::try_from( + "ethereum".as_bytes().to_vec(), + ) + .unwrap(), + _marker: Default::default(), + liquidity_pools_contract_address, + }; - #[test_runtimes(all)] - fn xtokens_transfer_multiassets() { - let mut env = FudgeEnv::::default(); + let test_router = DomainRouter::::AxelarEVM(axelar_evm_router); - let tranche_currency = CurrencyId::Tranche(401, [0; 16]); - let tranche_id = - WeakBoundedVec::>::force_from(tranche_currency.encode(), None); - let tranche_location = Location::new( - 1, - [ - Parachain(123), - PalletInstance(42), - GeneralKey { - length: tranche_id.len() as u8, - data: vec_to_fixed_array(tranche_id), - }, - ], + let set_domain_router_call = + set_domain_router_call(test_domain.clone(), test_router.clone()); + + let council_threshold = 2; + let voting_period = 3; + + execute_via_democracy::( + &mut env, + get_council_members(), + set_domain_router_call, + council_threshold, + voting_period, + 0, + 0, + ); + + env.parachain_state(|| { + let router = pallet_liquidity_pools_gateway::Pallet::::domain_routers(test_domain) + .expect("domain router is set"); + + assert!(router.eq(&test_router)); + }); + } + + #[test_runtimes([development])] + fn add_remove_instances() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::council_members::(get_council_members())) + .storage(), + ); + + let test_instance = DomainAddress::EVM(1, [0; 20]); + + let add_instance_call = add_instance_call::(test_instance.clone()); + + let council_threshold = 2; + let voting_period = 3; + + let (prop_index, ref_index) = execute_via_democracy::( + &mut env, + get_council_members(), + add_instance_call, + council_threshold, + voting_period, + 0, + 0, + ); + + env.parachain_state(|| { + assert!( + pallet_liquidity_pools_gateway::Allowlist::::contains_key( + test_instance.domain(), + test_instance.clone() + ) ); - let tranche_asset = Asset::from((AssetId(tranche_location), Fungibility::Fungible(42))); + }); - env.parachain_state_mut(|| { - assert_noop!( - orml_xtokens::Pallet::::transfer_multiassets( - RawOrigin::Signed(Keyring::Alice.into()).into(), - Box::new(VersionedAssets::from(Assets::from(vec![tranche_asset]))), - 0, - Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - network: None, - id: Keyring::Bob.into(), - } - ] - ) - .into() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - ), - orml_xtokens::Error::::XcmExecutionFailed - ); - }); - } + let remove_instance_call = remove_instance_call::(test_instance.clone()); + + execute_via_democracy::( + &mut env, + get_council_members(), + remove_instance_call, + council_threshold, + voting_period, + prop_index, + ref_index, + ); + + env.parachain_state(|| { + assert!( + !pallet_liquidity_pools_gateway::Allowlist::::contains_key( + test_instance.domain(), + test_instance.clone() + ) + ); + }); + } + + #[test_runtimes([development])] + fn process_msg() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .add(genesis::council_members::(get_council_members())) + .storage(), + ); + + let test_instance = DomainAddress::EVM(1, [0; 20]); + + let add_instance_call = add_instance_call::(test_instance.clone()); + + let council_threshold = 2; + let voting_period = 3; + + execute_via_democracy::( + &mut env, + get_council_members(), + add_instance_call, + council_threshold, + voting_period, + 0, + 0, + ); + + env.parachain_state(|| { + assert!( + pallet_liquidity_pools_gateway::Allowlist::::contains_key( + test_instance.domain(), + test_instance.clone() + ) + ); + }); + + let msg = LiquidityPoolMessage::AddPool { pool_id: 123 }; + + let encoded_msg = msg.serialize(); + + let gateway_msg = BoundedVec::< + u8, + ::MaxIncomingMessageSize, + >::try_from(encoded_msg) + .unwrap(); + + env.parachain_state_mut(|| { + assert_noop!( + pallet_liquidity_pools_gateway::Pallet::::process_msg( + GatewayOrigin::Domain(test_instance).into(), + gateway_msg, + ), + pallet_liquidity_pools::Error::::InvalidIncomingMessage, + ); + }); } } diff --git a/runtime/integration-tests/src/generic/cases/loans.rs b/runtime/integration-tests/src/generic/cases/loans.rs index 657724a01e..61f6ce7349 100644 --- a/runtime/integration-tests/src/generic/cases/loans.rs +++ b/runtime/integration-tests/src/generic/cases/loans.rs @@ -76,7 +76,7 @@ mod common { .add(genesis::balances::( T::ExistentialDeposit::get() + FOR_FEES, )) - .add(genesis::assets::(vec![Box::new(Usd6)])) + .add(genesis::assets::(vec![(Usd6.id(), &Usd6.metadata())])) .add(genesis::tokens::(vec![(Usd6.id(), Usd6.ed())])) .storage(), ); diff --git a/runtime/integration-tests/src/generic/cases/lp/mod.rs b/runtime/integration-tests/src/generic/cases/lp/mod.rs index d10a514f0b..87669e43ec 100644 --- a/runtime/integration-tests/src/generic/cases/lp/mod.rs +++ b/runtime/integration-tests/src/generic/cases/lp/mod.rs @@ -162,11 +162,14 @@ pub mod utils { } pub fn verify_outbound_success( - _: ::Message, + message: ::Message, ) { assert!(matches!( last_event::>(), - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { .. } + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { + message: processed_message, + .. + } if processed_message == message )); } diff --git a/runtime/integration-tests/src/generic/cases/lp/transfers.rs b/runtime/integration-tests/src/generic/cases/lp/transfers.rs index af772f9f56..864f3f43c0 100644 --- a/runtime/integration-tests/src/generic/cases/lp/transfers.rs +++ b/runtime/integration-tests/src/generic/cases/lp/transfers.rs @@ -143,13 +143,13 @@ fn transfer_tokens_from_local() { utils::prepare_hold_usdc_local::(&mut env); env.state_mut(|_evm| { - let call = pallet_liquidity_pools::Pallet::::transfer( + pallet_liquidity_pools::Pallet::::transfer( OriginFor::::signed(Keyring::Ferdie.into()), USDC.id(), DomainAddress::evm(EVM_DOMAIN_CHAIN_ID, Keyring::Ferdie.into()), AMOUNT, - ); - call.unwrap(); + ) + .unwrap(); lp::utils::process_outbound::(lp::utils::verify_outbound_success::); }); diff --git a/runtime/integration-tests/src/generic/cases/precompile.rs b/runtime/integration-tests/src/generic/cases/precompile.rs index e438ec8149..d7f71c405a 100644 --- a/runtime/integration-tests/src/generic/cases/precompile.rs +++ b/runtime/integration-tests/src/generic/cases/precompile.rs @@ -30,7 +30,7 @@ use crate::generic::{ fn axelar_precompile_execute() { RuntimeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::assets::(vec![Box::new(Usd18)])) + .add(genesis::assets::([(Usd18.id(), &Usd18.metadata())])) .storage(), ) .parachain_state_mut(|| { diff --git a/runtime/integration-tests/src/generic/cases/proxy.rs b/runtime/integration-tests/src/generic/cases/proxy.rs index fdd1781e28..a1ce753c5e 100644 --- a/runtime/integration-tests/src/generic/cases/proxy.rs +++ b/runtime/integration-tests/src/generic/cases/proxy.rs @@ -1,27 +1,18 @@ -use cfg_primitives::Balance; -use cfg_types::{tokens::CrossChainTransferability, xcm::XcmMetadata}; -use frame_support::{assert_err, assert_ok, traits::Get}; +use cfg_types::tokens::{AssetMetadata, CurrencyId}; +use frame_support::{assert_err, assert_ok}; use frame_system::RawOrigin; use sp_runtime::{traits::StaticLookup, DispatchResult}; -use staging_xcm::{ - prelude::Parachain, - v4::{Junction, Location, WeightLimit}, - VersionedLocation, -}; +use staging_xcm::v4::WeightLimit; use crate::{ generic::{ config::Runtime, env::Env, - envs::{ - fudge_env::{handle::FudgeHandle, FudgeEnv, FudgeSupport}, - runtime_env::RuntimeEnv, - }, + envs::runtime_env::RuntimeEnv, utils::{ - self, - currency::{cfg, register_currency, usd6, CurrencyInfo, Usd6}, + currency::{cfg, CurrencyInfo, CustomCurrency}, genesis::{self, Genesis}, - xcm::setup_xcm, + xcm::{account_location, transferable_metadata}, }, }, utils::accounts::Keyring, @@ -31,107 +22,73 @@ const FROM: Keyring = Keyring::Charlie; const PROXY: Keyring = Keyring::Alice; const TO: Keyring = Keyring::Bob; -const FOR_FEES: Balance = cfg(1); -const TRANSFER_AMOUNT: Balance = usd6(100); +enum TransferKind { + Local, + Xcm, +} -fn configure_proxy_and_transfer(proxy_type: T::ProxyType) -> DispatchResult { - let env = RuntimeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::( - T::ExistentialDeposit::get() + FOR_FEES, - )) - .add(genesis::tokens::(vec![(Usd6.id(), Usd6.ed())])) - .storage(), +fn run_test(proxy_type: T::ProxyType, transfer_kind: TransferKind) -> DispatchResult { + let para_id = 1234; + let curr = CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals: 6, + ..transferable_metadata(Some(para_id)) + }, ); - let call = pallet_restricted_tokens::Call::transfer { - currency_id: Usd6.id(), - amount: TRANSFER_AMOUNT, - dest: T::Lookup::unlookup(TO.id()), - } - .into(); - - configure_proxy_and_call::(env, proxy_type, call) -} - -fn configure_proxy_and_x_transfer( - proxy_type: T::ProxyType, -) -> DispatchResult { - let mut env = FudgeEnv::::from_parachain_storage( + let mut env = RuntimeEnv::::from_parachain_storage( Genesis::default() - .add(genesis::balances::( - T::ExistentialDeposit::get() + FOR_FEES, - )) - .add(genesis::tokens::(vec![(Usd6.id(), Usd6.ed())])) + .add(genesis::balances::(cfg(1))) // For fees + .add(genesis::tokens::(vec![(curr.id(), curr.val(1000))])) + .add(genesis::assets::(vec![(curr.id(), curr.metadata())])) .storage(), ); - setup_xcm(&mut env); - - env.parachain_state_mut(|| { - register_currency::(Usd6, |meta| { - meta.location = Some(VersionedLocation::V4(Location::new( - 1, - Parachain(T::FudgeHandle::SIBLING_ID), - ))); - meta.additional.transferability = CrossChainTransferability::Xcm(XcmMetadata { - fee_per_second: Some(1_000), - }); - }); - }); - - let call = pallet_restricted_xtokens::Call::transfer { - currency_id: Usd6.id(), - amount: TRANSFER_AMOUNT, - dest: Box::new( - Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - Junction::AccountId32 { - id: TO.into(), - network: None, - }, - ], - ) - .into(), - ), - dest_weight_limit: WeightLimit::Unlimited, - } - .into(); - - configure_proxy_and_call::(env, proxy_type, call) -} + let call = match transfer_kind { + TransferKind::Local => pallet_restricted_tokens::Call::transfer { + currency_id: curr.id(), + amount: curr.val(100), + dest: T::Lookup::unlookup(TO.id()), + } + .into(), + TransferKind::Xcm => pallet_restricted_xtokens::Call::transfer { + currency_id: curr.id(), + amount: curr.val(100), + dest: account_location(1, Some(para_id), TO.id()), + dest_weight_limit: WeightLimit::Unlimited, + } + .into(), + }; -fn configure_proxy_and_call( - mut env: impl Env, - proxy_type: T::ProxyType, - call: T::RuntimeCallExt, -) -> DispatchResult { env.parachain_state_mut(|| { - utils::give_tokens::(FROM.id(), Usd6.id(), TRANSFER_AMOUNT); - // Register PROXY as proxy of FROM - pallet_proxy::Pallet::::add_proxy( + assert_ok!(pallet_proxy::Pallet::::add_proxy( RawOrigin::Signed(FROM.id()).into(), T::Lookup::unlookup(PROXY.id()), proxy_type, 0, - ) - .unwrap(); + )); // Acts as FROM using PROXY - pallet_proxy::Pallet::::proxy( + assert_ok!(pallet_proxy::Pallet::::proxy( RawOrigin::Signed(PROXY.id()).into(), T::Lookup::unlookup(FROM.id()), None, Box::new(call), - ) - .unwrap(); + )); }); env.find_event(|e| match e { - pallet_proxy::Event::::ProxyExecuted { result } => Some(result), + pallet_proxy::Event::::ProxyExecuted { result } => { + if result == Err(orml_xtokens::Error::::XcmExecutionFailed.into()) { + // We have not configured XCM, so if we reach the sending phase though xcm we + // can assert that proxy was filtered correctly. + Some(Ok(())) + } else { + Some(result) + } + } _ => None, }) .unwrap() @@ -142,8 +99,9 @@ fn development_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_transfer::( - development_runtime::ProxyType::Transfer + assert_ok!(run_test::( + development_runtime::ProxyType::Transfer, + TransferKind::Local )); } @@ -153,7 +111,7 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(development_runtime::ProxyType::Borrow), + run_test::(development_runtime::ProxyType::Borrow, TransferKind::Local), frame_system::Error::::CallFiltered, ); } @@ -164,39 +122,40 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(development_runtime::ProxyType::Invest), + run_test::(development_runtime::ProxyType::Invest, TransferKind::Local), frame_system::Error::::CallFiltered, ); } #[test_runtimes([development])] -fn development_x_transfer_with_proxy_transfer() +fn development_x_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_x_transfer::( - development_runtime::ProxyType::Transfer + assert_ok!(run_test::( + development_runtime::ProxyType::Transfer, + TransferKind::Xcm )); } #[test_runtimes([development])] -fn development_x_transfer_with_proxy_borrow() +fn development_x_transfer_with_proxy_borrow() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(development_runtime::ProxyType::Borrow), + run_test::(development_runtime::ProxyType::Borrow, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } #[test_runtimes([development])] -fn development_x_transfer_with_proxy_invest() +fn development_x_transfer_with_proxy_invest() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(development_runtime::ProxyType::Invest), + run_test::(development_runtime::ProxyType::Invest, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } @@ -206,8 +165,9 @@ fn altair_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_transfer::( - altair_runtime::ProxyType::Transfer + assert_ok!(run_test::( + altair_runtime::ProxyType::Transfer, + TransferKind::Local )); } @@ -217,7 +177,7 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(altair_runtime::ProxyType::Borrow), + run_test::(altair_runtime::ProxyType::Borrow, TransferKind::Local), frame_system::Error::::CallFiltered, ); } @@ -228,39 +188,40 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(altair_runtime::ProxyType::Invest), + run_test::(altair_runtime::ProxyType::Invest, TransferKind::Local), frame_system::Error::::CallFiltered, ); } #[test_runtimes([altair])] -fn altair_x_transfer_with_proxy_transfer() +fn altair_x_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_x_transfer::( - altair_runtime::ProxyType::Transfer + assert_ok!(run_test::( + altair_runtime::ProxyType::Transfer, + TransferKind::Xcm )); } #[test_runtimes([altair])] -fn altair_x_transfer_with_proxy_borrow() +fn altair_x_transfer_with_proxy_borrow() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(altair_runtime::ProxyType::Borrow), + run_test::(altair_runtime::ProxyType::Borrow, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } #[test_runtimes([altair])] -fn altair_x_transfer_with_proxy_invest() +fn altair_x_transfer_with_proxy_invest() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(altair_runtime::ProxyType::Invest), + run_test::(altair_runtime::ProxyType::Invest, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } @@ -270,8 +231,9 @@ fn centrifuge_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_transfer::( - centrifuge_runtime::ProxyType::Transfer + assert_ok!(run_test::( + centrifuge_runtime::ProxyType::Transfer, + TransferKind::Local )); } @@ -281,7 +243,7 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(centrifuge_runtime::ProxyType::Borrow), + run_test::(centrifuge_runtime::ProxyType::Borrow, TransferKind::Local), frame_system::Error::::CallFiltered, ); } @@ -292,39 +254,40 @@ where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_transfer::(centrifuge_runtime::ProxyType::Invest), + run_test::(centrifuge_runtime::ProxyType::Invest, TransferKind::Local), frame_system::Error::::CallFiltered, ); } #[test_runtimes([centrifuge])] -fn centrifuge_x_transfer_with_proxy_transfer() +fn centrifuge_x_transfer_with_proxy_transfer() where T: pallet_proxy::Config, { - assert_ok!(configure_proxy_and_x_transfer::( - centrifuge_runtime::ProxyType::Transfer + assert_ok!(run_test::( + centrifuge_runtime::ProxyType::Transfer, + TransferKind::Xcm )); } #[test_runtimes([centrifuge])] -fn centrifuge_x_transfer_with_proxy_borrow() +fn centrifuge_x_transfer_with_proxy_borrow() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(centrifuge_runtime::ProxyType::Borrow), + run_test::(centrifuge_runtime::ProxyType::Borrow, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } #[test_runtimes([centrifuge])] -fn centrifuge_x_transfer_with_proxy_invest() +fn centrifuge_x_transfer_with_proxy_invest() where T: pallet_proxy::Config, { assert_err!( - configure_proxy_and_x_transfer::(centrifuge_runtime::ProxyType::Invest), + run_test::(centrifuge_runtime::ProxyType::Invest, TransferKind::Xcm), frame_system::Error::::CallFiltered, ); } diff --git a/runtime/integration-tests/src/generic/cases/restricted_transfers.rs b/runtime/integration-tests/src/generic/cases/restricted_transfers.rs index ab1af8deb7..3d7725c20a 100644 --- a/runtime/integration-tests/src/generic/cases/restricted_transfers.rs +++ b/runtime/integration-tests/src/generic/cases/restricted_transfers.rs @@ -10,34 +10,42 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -mod cfg { - use cfg_primitives::{currency_decimals, Balance}; - use cfg_types::{ - locations::RestrictedTransferLocation, - tokens::{CurrencyId, FilterCurrency}, - }; - use frame_support::{assert_ok, dispatch::RawOrigin}; - use runtime_common::remarks::Remark; - use sp_runtime::traits::Zero; - - use crate::{ - generic::{ - config::Runtime, - env::Env, - envs::runtime_env::RuntimeEnv, - utils::{genesis, genesis::Genesis}, +use cfg_primitives::Balance; +use cfg_types::{ + domain_address::DomainAddress, + locations::RestrictedTransferLocation, + tokens::{ + AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata, FilterCurrency, + }, +}; +use cumulus_primitives_core::WeightLimit; +use frame_support::{assert_noop, assert_ok, dispatch::RawOrigin, traits::PalletInfo}; +use runtime_common::remarks::Remark; +use sp_runtime::traits::Zero; +use staging_xcm::{ + v4::{Junction::*, Location, NetworkId}, + VersionedLocation, +}; + +use crate::{ + generic::{ + config::Runtime, + env::Env, + envs::runtime_env::RuntimeEnv, + utils::{ + currency::{cfg, default_metadata, CurrencyInfo, CustomCurrency}, + genesis, + genesis::Genesis, + xcm::{account_location, transferable_metadata}, }, - utils::accounts::Keyring, - }; + }, + utils::accounts::Keyring, +}; - const TRANSFER_AMOUNT: Balance = 100; +mod local { + use super::*; - pub fn decimals(decimals: u32) -> Balance { - 10u128.saturating_pow(decimals) - } - pub fn cfg(amount: Balance) -> Balance { - amount * decimals(currency_decimals::NATIVE) - } + const TRANSFER_AMOUNT: u32 = 100; fn setup(filter: FilterCurrency) -> RuntimeEnv { let mut env = RuntimeEnv::::from_parachain_storage( @@ -66,155 +74,76 @@ mod cfg { env } - fn validate_fail(who: Keyring, call: impl Into + Clone) { - // With FilterCurrencyAll - { - let mut env = setup::(FilterCurrency::All); - - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = - env.parachain_state(|| { - // NOTE: The para-id is not relevant here - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let fee = env.submit_now(who, call.clone()).unwrap(); - // NOTE: Only use fee, if submitter is Alice - let fee = if who != Keyring::Alice { 0 } else { fee }; - - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!(after_transfer_alice, pre_transfer_alice - fee); - assert_eq!(after_transfer_bob, pre_transfer_bob); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + fn people_balances() -> (Balance, Balance, Balance) { + ( + pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), + pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), + pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), + ) + } - // With FilterCurrency::Specific(CurrencyId::Native) - { - let mut env = setup::(FilterCurrency::Specific(CurrencyId::Native)); - - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = - env.parachain_state(|| { - // NOTE: The para-id is not relevant here - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let fee = env.submit_now(who, call).unwrap(); - // NOTE: Only use fee, if submitter is Alice - let fee = if who != Keyring::Alice { 0 } else { fee }; - - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!(after_transfer_alice, pre_transfer_alice - fee); - assert_eq!(after_transfer_bob, pre_transfer_bob); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + fn process_ok( + env: &mut RuntimeEnv, + who: Keyring, + call: impl Into, + ) { + let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = + env.parachain_state(|| people_balances::()); + + let fee = env.submit_now(who, call).unwrap(); + // NOTE: Only use fee, if submitter is Alice + let fee = if who != Keyring::Alice { 0 } else { fee }; + + let (after_transfer_alice, after_transfer_bob, after_transfer_charlie) = + env.parachain_state(|| people_balances::()); + + assert_eq!( + after_transfer_alice, + pre_transfer_alice - fee - cfg(TRANSFER_AMOUNT) + ); + assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); + } + + fn process_fail( + env: &mut RuntimeEnv, + who: Keyring, + call: impl Into, + ) { + let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = + env.parachain_state(|| people_balances::()); + + let fee = env.submit_now(who, call).unwrap(); + // NOTE: Only use fee, if submitter is Alice + let fee = if who != Keyring::Alice { 0 } else { fee }; + + let (after_transfer_alice, after_transfer_bob, after_transfer_charlie) = + env.parachain_state(|| people_balances::()); + + assert_eq!(after_transfer_alice, pre_transfer_alice - fee); + assert_eq!(after_transfer_bob, pre_transfer_bob); + assert_eq!(after_transfer_charlie, pre_transfer_charlie); } fn validate_ok(who: Keyring, call: impl Into + Clone) { - // With FilterCurrencyAll - { - let mut env = setup::(FilterCurrency::All); - - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = - env.parachain_state(|| { - // NOTE: The para-id is not relevant here - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let fee = env.submit_now(who, call.clone()).unwrap(); - - // NOTE: Only use fee, if submitter is Alice - let fee = if who != Keyring::Alice { 0 } else { fee }; - - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!( - after_transfer_alice, - pre_transfer_alice - fee - cfg(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + let mut env = setup::(FilterCurrency::All); + process_ok(&mut env, who, call.clone()); - // With FilterCurrency::Specific(CurrencyId::Native) - { - let mut env = setup::(FilterCurrency::Specific(CurrencyId::Native)); - - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = - env.parachain_state(|| { - // NOTE: The para-id is not relevant here - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let fee = env.submit_now(who, call).unwrap(); - // NOTE: Only use fee, if submitter is Alice - let fee = if who != Keyring::Alice { 0 } else { fee }; - - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!( - after_transfer_alice, - pre_transfer_alice - fee - cfg(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + let mut env = setup::(FilterCurrency::Specific(CurrencyId::Native)); + process_ok(&mut env, who, call.clone()); } - fn transfer_ok() -> pallet_balances::Call { - pallet_balances::Call::::transfer_allow_death { - dest: Keyring::Bob.into(), - value: cfg(TRANSFER_AMOUNT), - } + fn validate_fail(who: Keyring, call: impl Into + Clone) { + let mut env = setup::(FilterCurrency::All); + process_fail(&mut env, who, call.clone()); + + let mut env = setup::(FilterCurrency::Specific(CurrencyId::Native)); + process_fail(&mut env, who, call.clone()); } - fn transfer_fail() -> pallet_balances::Call { - pallet_balances::Call::::transfer_allow_death { - dest: Keyring::Charlie.into(), + fn transfer_to(dest: Keyring) -> pallet_balances::Call { + pallet_balances::Call::transfer_allow_death { + dest: dest.into(), value: cfg(TRANSFER_AMOUNT), } } @@ -227,38 +156,13 @@ mod cfg { .storage(), ); - let (pre_transfer_alice, pre_transfer_bob, pre_transfer_charlie) = - env.parachain_state(|| { - // NOTE: The para-id is not relevant here - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), - ) - }); - - let fee = env.submit_now(Keyring::Alice, transfer_ok::()).unwrap(); - - env.parachain_state(|| { - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_bob = pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!( - after_transfer_alice, - pre_transfer_alice - fee - cfg(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); + process_ok(&mut env, Keyring::Alice, transfer_to(Keyring::Bob)); } #[test_runtimes(all)] fn basic_transfer() { - validate_ok::(Keyring::Alice, transfer_ok::()); - validate_fail::(Keyring::Alice, transfer_fail::()); + validate_ok::(Keyring::Alice, transfer_to(Keyring::Bob)); + validate_fail::(Keyring::Alice, transfer_to(Keyring::Charlie)); } #[test_runtimes(all)] @@ -268,7 +172,7 @@ mod cfg { pallet_proxy::Call::::proxy { real: Keyring::Alice.into(), force_proxy_type: None, - call: Box::new(transfer_ok::().into()), + call: Box::new(transfer_to(Keyring::Bob).into()), }, ); validate_fail::( @@ -276,7 +180,7 @@ mod cfg { pallet_proxy::Call::::proxy { real: Keyring::Alice.into(), force_proxy_type: None, - call: Box::new(transfer_fail::().into()), + call: Box::new(transfer_to(Keyring::Charlie).into()), }, ); } @@ -290,7 +194,7 @@ mod cfg { force_proxy_type: None, call: Box::new( pallet_utility::Call::::batch { - calls: vec![transfer_ok::().into()], + calls: vec![transfer_to(Keyring::Bob).into()], } .into(), ), @@ -303,7 +207,7 @@ mod cfg { force_proxy_type: None, call: Box::new( pallet_utility::Call::::batch { - calls: vec![transfer_fail::().into()], + calls: vec![transfer_to(Keyring::Charlie).into()], } .into(), ), @@ -316,16 +220,16 @@ mod cfg { validate_ok::( Keyring::Alice, pallet_utility::Call::::batch { - calls: vec![transfer_ok::().into()], + calls: vec![transfer_to(Keyring::Bob).into()], }, ); validate_fail::( Keyring::Alice, pallet_utility::Call::::batch { calls: vec![ - transfer_fail::().into(), - transfer_fail::().into(), - transfer_fail::().into(), + transfer_to(Keyring::Charlie).into(), + transfer_to(Keyring::Charlie).into(), + transfer_to(Keyring::Charlie).into(), ], }, ); @@ -336,16 +240,16 @@ mod cfg { validate_ok::( Keyring::Alice, pallet_utility::Call::::batch_all { - calls: vec![transfer_ok::().into()], + calls: vec![transfer_to(Keyring::Bob).into()], }, ); validate_fail::( Keyring::Alice, pallet_utility::Call::::batch_all { calls: vec![ - transfer_fail::().into(), - transfer_fail::().into(), - transfer_fail::().into(), + transfer_to(Keyring::Charlie).into(), + transfer_to(Keyring::Charlie).into(), + transfer_to(Keyring::Charlie).into(), ], }, ); @@ -366,7 +270,7 @@ mod cfg { )] .try_into() .expect("Small enough. qed."), - call: Box::new(transfer_ok::().into()), + call: Box::new(transfer_to(Keyring::Bob).into()), }, ); validate_fail::( @@ -382,8 +286,149 @@ mod cfg { )] .try_into() .expect("Small enough. qed."), - call: Box::new(transfer_fail::().into()), + call: Box::new(transfer_to(Keyring::Charlie).into()), + }, + ); + } +} + +mod xcm { + use super::*; + + const TRANSFER: u32 = 100; + const PARA_ID: u32 = 1000; + + #[test_runtimes(all)] + fn restrict_xcm_transfer() { + let curr = CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals: 6, + ..transferable_metadata(Some(PARA_ID)) }, ); + + let mut env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1))) // For fees + .add(genesis::tokens::([(curr.id(), curr.val(TRANSFER))])) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + env.parachain_state_mut(|| { + assert_ok!( + pallet_transfer_allowlist::Pallet::::add_transfer_allowance( + RawOrigin::Signed(Keyring::Alice.into()).into(), + FilterCurrency::Specific(curr.id()), + RestrictedTransferLocation::Xcm(account_location( + 1, + Some(PARA_ID), + Keyring::Alice.id() + )), + ) + ); + + assert_noop!( + pallet_restricted_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + curr.id(), + curr.val(TRANSFER), + account_location(1, Some(PARA_ID), Keyring::Bob.id()), + WeightLimit::Unlimited, + ), + pallet_transfer_allowlist::Error::::NoAllowanceForDestination + ); + + assert_noop!( + pallet_restricted_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + curr.id(), + curr.val(TRANSFER), + account_location(1, Some(PARA_ID), Keyring::Alice.id()), + WeightLimit::Unlimited, + ), + // But it's ok, we do not care about the xcm transaction in this context. + // It is already checked at `xcm_transfers.rs` + orml_xtokens::Error::::XcmExecutionFailed + ); + }); + } +} + +mod eth_address { + use super::*; + + const TRANSFER: u32 = 10; + const CHAIN_ID: u64 = 1; + const CONTRACT_ACCOUNT: [u8; 20] = [1; 20]; + + #[test_runtimes(all)] + fn restrict_lp_eth_transfer() { + let pallet_index = T::PalletInfo::index::>(); + let curr = CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals: 6, + location: Some(VersionedLocation::V4(Location::new( + 0, + [ + PalletInstance(pallet_index.unwrap() as u8), + GlobalConsensus(NetworkId::Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { + network: None, + key: CONTRACT_ACCOUNT, + }, + ], + ))), + additional: CustomMetadata { + transferability: CrossChainTransferability::LiquidityPools, + ..CustomMetadata::default() + }, + ..default_metadata() + }, + ); + + let mut env = RuntimeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(10))) + .add(genesis::tokens::([(curr.id(), curr.val(TRANSFER))])) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + env.parachain_state_mut(|| { + let curr_contract = DomainAddress::EVM(CHAIN_ID, CONTRACT_ACCOUNT); + + assert_ok!( + pallet_transfer_allowlist::Pallet::::add_transfer_allowance( + RawOrigin::Signed(Keyring::Alice.into()).into(), + FilterCurrency::Specific(curr.id()), + RestrictedTransferLocation::Address(curr_contract.clone()), + ) + ); + + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + curr.id(), + DomainAddress::EVM(CHAIN_ID, [2; 20]), // Not the allowed contract account + curr.val(TRANSFER), + ), + pallet_transfer_allowlist::Error::::NoAllowanceForDestination + ); + + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.into()).into(), + curr.id(), + curr_contract, + curr.val(TRANSFER), + ), + // But it's ok, we do not care about the router transaction in this context. + // Is already checked at `liquidity_pools.rs` + pallet_liquidity_pools_gateway::Error::::RouterNotFound + ); + }); } } diff --git a/runtime/integration-tests/src/generic/cases/xcm_transfers.rs b/runtime/integration-tests/src/generic/cases/xcm_transfers.rs new file mode 100644 index 0000000000..04f471144b --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/xcm_transfers.rs @@ -0,0 +1,236 @@ +use cfg_types::tokens::{AssetMetadata, CurrencyId, CurrencyId::Native}; +use frame_support::{assert_ok, dispatch::RawOrigin}; +use orml_traits::MultiCurrency; +use staging_xcm::v4::{Junction::*, Junctions::Here, WeightLimit}; + +use crate::{ + generic::{ + config::Runtime, + env::{Blocks, Env}, + envs::fudge_env::{ + handle::{PARA_ID, SIBLING_ID}, + FudgeEnv, FudgeSupport, RelayRuntime, + }, + utils::{ + currency::{cfg, CurrencyInfo, CustomCurrency}, + genesis, + genesis::Genesis, + xcm::{ + account_location, enable_para_to_relay_communication, + enable_para_to_sibling_communication, enable_relay_to_para_communication, + transferable_metadata, + }, + }, + }, + utils::{accounts::Keyring, approx::Approximate}, +}; + +const INITIAL: u32 = 100; +const TRANSFER: u32 = 20; + +fn create_transferable_currency(decimals: u32, para_id: Option) -> CustomCurrency { + CustomCurrency( + CurrencyId::ForeignAsset(1), + AssetMetadata { + decimals, + ..transferable_metadata(para_id) + }, + ) +} + +#[test_runtimes(all)] +fn para_to_sibling_with_foreign_to_foreign_tokens() { + let curr = create_transferable_currency(6, Some(PARA_ID)); + + let mut env = FudgeEnv::::from_storage( + Default::default(), + Genesis::default() + .add(genesis::tokens::([(curr.id(), curr.val(INITIAL))])) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + Genesis::default() + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + enable_para_to_sibling_communication::(&mut env); + + env.parachain_state_mut(|| { + assert_ok!(orml_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.id()).into(), + curr.id(), + curr.val(TRANSFER), + account_location(1, Some(SIBLING_ID), Keyring::Bob.id()), + WeightLimit::Unlimited, + )); + + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Alice.id()), + curr.val(INITIAL) - curr.val(TRANSFER) + ); + }); + + env.pass(Blocks::ByNumber(2)); + + env.sibling_state(|| { + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Bob.id()), + curr.val(TRANSFER) + ); + }); +} + +#[test_runtimes(all)] +fn para_to_sibling_with_native_to_foreign_tokens() { + let curr = create_transferable_currency(18, Some(PARA_ID)); + + let mut env = FudgeEnv::::from_storage( + Default::default(), + Genesis::default() + .add(genesis::balances::(cfg(INITIAL))) + .add(genesis::assets::([(Native, curr.metadata())])) + .storage(), + Genesis::default() + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + ); + + enable_para_to_sibling_communication::(&mut env); + + env.parachain_state_mut(|| { + assert_ok!(orml_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.id()).into(), + Native, + cfg(TRANSFER), + account_location(1, Some(SIBLING_ID), Keyring::Bob.id()), + WeightLimit::Unlimited, + )); + + assert_eq!( + pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), + cfg(INITIAL) - cfg(TRANSFER) + ); + }); + + env.pass(Blocks::ByNumber(2)); + + env.sibling_state(|| { + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Bob.id()), + curr.val(TRANSFER) + ); + }); +} + +#[test_runtimes(all)] +fn para_to_sibling_with_foreign_to_native_tokens() { + let curr = create_transferable_currency(18, Some(PARA_ID)); + + let mut env = FudgeEnv::::from_storage( + Default::default(), + Genesis::default() + .add(genesis::tokens::([(curr.id(), curr.val(INITIAL))])) + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + Genesis::default() + .add(genesis::assets::([(Native, curr.metadata())])) + .storage(), + ); + + enable_para_to_sibling_communication::(&mut env); + + env.parachain_state_mut(|| { + assert_ok!(orml_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Alice.id()).into(), + curr.id(), + curr.val(TRANSFER), + account_location(1, Some(SIBLING_ID), Keyring::Bob.id()), + WeightLimit::Unlimited, + )); + + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Alice.id()), + curr.val(INITIAL) - curr.val(TRANSFER) + ); + }); + + env.pass(Blocks::ByNumber(2)); + + env.sibling_state(|| { + assert_eq!( + pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), + cfg(TRANSFER) + ); + }); +} + +#[test_runtimes(all)] +fn para_from_to_relay_using_relay_native_tokens() { + let curr = create_transferable_currency(10, None); + + let mut env = FudgeEnv::::from_storage( + Genesis::default() + .add(genesis::balances::>(curr.val(INITIAL))) + .storage(), + Genesis::default() + .add(genesis::assets::([(curr.id(), curr.metadata())])) + .storage(), + Default::default(), + ); + + // From Relay to Parachain + enable_relay_to_para_communication::(&mut env); + + env.relay_state_mut(|| { + assert_ok!( + pallet_xcm::Pallet::>::reserve_transfer_assets( + RawOrigin::Signed(Keyring::Alice.id()).into(), + Box::new(Parachain(PARA_ID).into()), + account_location(0, None, Keyring::Bob.id()), + Box::new((Here, curr.val(TRANSFER)).into()), + 0, + ) + ); + + assert_eq!( + pallet_balances::Pallet::>::free_balance(&Keyring::Alice.id()), + (curr.val(INITIAL) - curr.val(TRANSFER)).approx(0.01) + ); + }); + + env.pass(Blocks::ByNumber(2)); + + env.parachain_state(|| { + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Bob.id()), + curr.val(TRANSFER) + ); + }); + + // From Parachain to Relay + enable_para_to_relay_communication::(&mut env); + + env.parachain_state_mut(|| { + assert_ok!(orml_xtokens::Pallet::::transfer( + RawOrigin::Signed(Keyring::Bob.id()).into(), + curr.id(), + curr.val(TRANSFER / 2), + account_location(1, None, Keyring::Alice.id()), + WeightLimit::Unlimited, + )); + + assert_eq!( + orml_tokens::Pallet::::free_balance(curr.id(), &Keyring::Bob.id()), + (curr.val(TRANSFER) - curr.val(TRANSFER / 2)).approx(0.01) + ); + }); + + env.pass(Blocks::ByNumber(2)); + + env.relay_state(|| { + assert_eq!( + pallet_balances::Pallet::>::free_balance(&Keyring::Alice.id()), + (curr.val(INITIAL) - curr.val(TRANSFER) + curr.val(TRANSFER / 2)).approx(0.01) + ); + }); +} diff --git a/runtime/integration-tests/src/generic/envs/fudge_env.rs b/runtime/integration-tests/src/generic/envs/fudge_env.rs index 214b3037a5..4c9cc28d0c 100644 --- a/runtime/integration-tests/src/generic/envs/fudge_env.rs +++ b/runtime/integration-tests/src/generic/envs/fudge_env.rs @@ -24,7 +24,7 @@ pub trait FudgeSupport: Runtime { type FudgeHandle: FudgeHandle; } -pub type FudgeRelayRuntime = <::FudgeHandle as FudgeHandle>::RelayRuntime; +pub type RelayRuntime = <::FudgeHandle as FudgeHandle>::RelayRuntime; /// Evironment that uses fudge to interact with the runtime pub struct FudgeEnv { @@ -48,6 +48,8 @@ impl Env for FudgeEnv { parachain_storage: Storage, sibling_storage: Storage, ) -> Self { + crate::utils::logs::init_logs(); + let mut handle = T::FudgeHandle::new(relay_storage, parachain_storage, sibling_storage); handle.evolve(); diff --git a/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs b/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs index 0e5321df5e..07d3af10e7 100644 --- a/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs +++ b/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs @@ -32,6 +32,8 @@ use crate::generic::config::Runtime; /// Start date used for timestamps in test-enviornments /// Sat Jan 01 2022 00:00:00 GMT+0000 pub const START_DATE: u64 = 1640995200u64; +pub const PARA_ID: u32 = 1001; +pub const SIBLING_ID: u32 = 1002; type InherentCreator = Box< dyn CreateInherentDataProviders< @@ -118,9 +120,6 @@ pub trait FudgeHandle { const RELAY_CODE: Option<&'static [u8]>; const PARACHAIN_CODE: Option<&'static [u8]>; - const PARA_ID: u32; - const SIBLING_ID: u32; - fn relay(&self) -> &RelaychainBuilder; fn relay_mut(&mut self) -> &mut RelaychainBuilder; @@ -146,8 +145,6 @@ pub trait FudgeHandle { storage: Storage, session_keys: ::Keys, ) -> RelaychainBuilder { - crate::utils::logs::init_logs(); - sp_tracing::enter_span!(sp_tracing::Level::INFO, "Relay - StartUp"); let code = Self::RELAY_CODE.expect("ESSENTIAL: WASM is built."); diff --git a/runtime/integration-tests/src/generic/impls.rs b/runtime/integration-tests/src/generic/impls.rs index 764c40ee27..8c0791ac0d 100644 --- a/runtime/integration-tests/src/generic/impls.rs +++ b/runtime/integration-tests/src/generic/impls.rs @@ -46,8 +46,6 @@ macro_rules! impl_fudge_support { $relay_path:ident, $relay_session_keys:expr, $parachain_path:ident, - $parachain_id:literal, - $sibling_id:literal ) => { const _: () = { use fudge::primitives::{Chain, ParaId}; @@ -58,6 +56,7 @@ macro_rules! impl_fudge_support { use crate::generic::envs::fudge_env::{ handle::{ FudgeHandle, ParachainBuilder, ParachainClient, RelayClient, RelaychainBuilder, + PARA_ID, SIBLING_ID, }, FudgeSupport, }; @@ -67,11 +66,11 @@ macro_rules! impl_fudge_support { #[fudge::relaychain] pub relay: RelaychainBuilder<$relay_path::RuntimeApi, $relay_path::Runtime>, - #[fudge::parachain($parachain_id)] + #[fudge::parachain(PARA_ID)] pub parachain: ParachainBuilder<$parachain_path::Block, $parachain_path::RuntimeApi>, - #[fudge::parachain($sibling_id)] + #[fudge::parachain(SIBLING_ID)] pub sibling: ParachainBuilder<$parachain_path::Block, $parachain_path::RuntimeApi>, } @@ -91,9 +90,7 @@ macro_rules! impl_fudge_support { type RelayRuntime = $relay_path::Runtime; const PARACHAIN_CODE: Option<&'static [u8]> = $parachain_path::WASM_BINARY; - const PARA_ID: u32 = $parachain_id; const RELAY_CODE: Option<&'static [u8]> = $relay_path::WASM_BINARY; - const SIBLING_ID: u32 = $sibling_id; fn new( relay_storage: Storage, @@ -102,12 +99,12 @@ macro_rules! impl_fudge_support { ) -> Self { let relay = Self::new_relay_builder(relay_storage, $relay_session_keys); let parachain = Self::new_parachain_builder( - ParaId::from(Self::PARA_ID), + ParaId::from(PARA_ID), &relay, parachain_storage, ); let sibling = Self::new_parachain_builder( - ParaId::from(Self::SIBLING_ID), + ParaId::from(SIBLING_ID), &relay, sibling_storage, ); @@ -183,8 +180,6 @@ impl_fudge_support!( rococo_runtime, default_rococo_session_keys(), development_runtime, - 2000, - 2001 ); impl_fudge_support!( @@ -192,8 +187,6 @@ impl_fudge_support!( rococo_runtime, default_rococo_session_keys(), altair_runtime, - 2088, - 2089 ); impl_fudge_support!( @@ -201,8 +194,6 @@ impl_fudge_support!( rococo_runtime, default_rococo_session_keys(), centrifuge_runtime, - 2031, - 2032 ); pub fn default_rococo_session_keys() -> rococo_runtime::SessionKeys { diff --git a/runtime/integration-tests/src/generic/mod.rs b/runtime/integration-tests/src/generic/mod.rs index 9d0f96e633..4dc2de2226 100644 --- a/runtime/integration-tests/src/generic/mod.rs +++ b/runtime/integration-tests/src/generic/mod.rs @@ -11,7 +11,9 @@ pub mod utils; // Test cases mod cases { mod account_derivation; + mod assets; mod block_rewards; + mod currency_conversions; mod ethereum_transaction; mod example; mod investments; @@ -22,6 +24,7 @@ mod cases { mod precompile; mod proxy; mod restricted_transfers; + mod xcm_transfers; } /// Generate tests for the specified runtimes or all runtimes. diff --git a/runtime/integration-tests/src/generic/utils/currency.rs b/runtime/integration-tests/src/generic/utils/currency.rs index 6d8b029e43..bec217c89e 100644 --- a/runtime/integration-tests/src/generic/utils/currency.rs +++ b/runtime/integration-tests/src/generic/utils/currency.rs @@ -5,11 +5,27 @@ use cfg_primitives::{conversion, liquidity_pools::GeneralCurrencyPrefix, Balance use cfg_types::tokens::{AssetMetadata, CurrencyId, CustomMetadata, GeneralCurrencyIndex}; use frame_support::{assert_ok, traits::OriginTrait}; use sp_runtime::FixedPointNumber; +use staging_xcm::VersionedLocation; use crate::generic::config::Runtime; -pub const fn cfg(amount: Balance) -> Balance { - amount * CFG +pub fn default_metadata() -> AssetMetadata { + AssetMetadata { + decimals: 0, + name: Default::default(), + symbol: Default::default(), + existential_deposit: 0, + location: None, + additional: Default::default(), + } +} + +const fn amount_pow(amount: Balance, exp: u32) -> Balance { + amount * 10u128.pow(exp) +} + +pub const fn cfg(amount: u32) -> Balance { + amount as Balance * CFG } pub trait CurrencyInfo { @@ -31,7 +47,7 @@ pub trait CurrencyInfo { &self.symbol() } - fn location(&self) -> Option { + fn location(&self) -> Option { None } @@ -89,7 +105,7 @@ impl CurrencyInfo for Usd6 { } pub const fn usd6(amount: Balance) -> Balance { - amount * 10u128.pow(6) + amount_pow(amount, 6) } pub struct Usd12; @@ -115,7 +131,7 @@ impl CurrencyInfo for Usd12 { } pub const fn usd12(amount: Balance) -> Balance { - amount * 10u128.pow(12) + amount_pow(amount, 12) } pub struct Usd18; @@ -141,7 +157,47 @@ impl CurrencyInfo for Usd18 { } pub const fn usd18(amount: Balance) -> Balance { - amount * 10u128.pow(18) + amount_pow(amount, 18) +} + +#[derive(Clone)] +pub struct CustomCurrency(pub CurrencyId, pub AssetMetadata); +impl CurrencyInfo for CustomCurrency { + fn id(&self) -> CurrencyId { + self.0 + } + + fn decimals(&self) -> u32 { + self.1.decimals + } + + fn symbol(&self) -> &'static str { + format!("Custom-{}", self.decimals()).leak() + } + + fn location(&self) -> Option { + self.1.location.clone() + } + + fn custom(&self) -> CustomMetadata { + self.1.additional + } + + fn metadata(&self) -> AssetMetadata { + self.1.clone() + } +} + +impl CustomCurrency { + // Using `u32` on purpose here to avoid placing a `Balance` as input and + // generating more decimals than expected. + pub const fn val(&self, amount: u32) -> Balance { + amount_pow(amount as Balance, self.1.decimals) + } + + pub fn metadata(&self) -> &AssetMetadata { + &self.1 + } } pub fn register_currency( diff --git a/runtime/integration-tests/src/generic/utils/genesis.rs b/runtime/integration-tests/src/generic/utils/genesis.rs index 6a656b872a..1648a95ba7 100644 --- a/runtime/integration-tests/src/generic/utils/genesis.rs +++ b/runtime/integration-tests/src/generic/utils/genesis.rs @@ -1,14 +1,17 @@ //! PLEASE be as much generic as possible because no domain or use cases are //! considered at this level. -use cfg_primitives::Balance; -use cfg_types::{fixed_point::Rate, tokens::CurrencyId}; +use cfg_primitives::{AccountId, Balance}; +use cfg_types::{ + fixed_point::Rate, + tokens::{AssetMetadata, CurrencyId}, +}; use parity_scale_codec::Encode; use sp_core::Get; use sp_runtime::{BuildStorage, FixedPointNumber, Storage}; use crate::{ - generic::{config::Runtime, utils::currency::CurrencyInfo}, + generic::config::Runtime, utils::accounts::{default_accounts, Keyring}, }; @@ -28,56 +31,62 @@ impl Genesis { } } -pub fn balances(balance: Balance) -> impl BuildStorage { - let mut accounts = Vec::new(); - accounts.extend(default_accounts().into_iter().map(|k| (k.id(), balance))); - accounts.extend( - default_accounts() +pub fn balances>( + balance: Balance, +) -> impl BuildStorage { + pallet_balances::GenesisConfig:: { + balances: default_accounts() .into_iter() - .map(|k| (k.id_ed25519(), balance)), - ); - - pallet_balances::GenesisConfig:: { balances: accounts } + .map(Keyring::id) + .chain(default_accounts().into_iter().map(Keyring::id_ed25519)) + .map(|id| (id, balance)) + .collect(), + } } -pub fn tokens(values: Vec<(CurrencyId, Balance)>) -> impl BuildStorage { - let mut accounts = Vec::new(); - accounts.extend(default_accounts().into_iter().flat_map(|keyring| { - values - .clone() - .into_iter() - .map(|(curency_id, balance)| (keyring.id(), curency_id, balance)) - .collect::>() - })); - accounts.extend(default_accounts().into_iter().flat_map(|keyring| { - values - .clone() +pub fn tokens( + values: impl IntoIterator + Clone, +) -> impl BuildStorage { + orml_tokens::GenesisConfig:: { + balances: default_accounts() .into_iter() - .map(|(curency_id, balance)| (keyring.id_ed25519(), curency_id, balance)) - .collect::>() - })); - - orml_tokens::GenesisConfig:: { balances: accounts } + .map(Keyring::id) + .chain(default_accounts().into_iter().map(Keyring::id_ed25519)) + .flat_map(|account_id| { + values + .clone() + .into_iter() + .map(|(curency_id, balance)| (account_id.clone(), curency_id, balance)) + .collect::>() + }) + .collect(), + } } -pub fn assets(currency_ids: Vec>) -> impl BuildStorage { +pub fn assets<'a, T: Runtime>( + currency_ids: impl IntoIterator, +) -> impl BuildStorage { orml_asset_registry::module::GenesisConfig:: { assets: currency_ids .into_iter() - .map(|currency_id| (currency_id.id(), currency_id.metadata().encode())) + .map(|(currency_id, metadata)| (currency_id, metadata.encode())) .collect(), last_asset_id: Default::default(), // It seems deprecated } } -pub fn council_members(members: Vec) -> impl BuildStorage { +pub fn council_members( + members: impl IntoIterator, +) -> impl BuildStorage { pallet_collective::GenesisConfig:: { phantom: Default::default(), members: members.into_iter().map(|acc| acc.id().into()).collect(), } } -pub fn invulnerables(invulnerables: Vec) -> impl BuildStorage { +pub fn invulnerables( + invulnerables: impl IntoIterator, +) -> impl BuildStorage { pallet_collator_selection::GenesisConfig:: { invulnerables: invulnerables.into_iter().map(|acc| acc.id()).collect(), candidacy_bond: cfg_primitives::MILLI_CFG, @@ -94,7 +103,9 @@ pub fn session_keys() -> impl BuildStorage { } } -pub fn block_rewards(collators: Vec) -> impl BuildStorage { +pub fn block_rewards( + collators: impl IntoIterator, +) -> impl BuildStorage { pallet_block_rewards::GenesisConfig:: { collators: collators.into_iter().map(|acc| acc.id()).collect(), collator_reward: (1000 * cfg_primitives::CFG).into(), @@ -102,3 +113,10 @@ pub fn block_rewards(collators: Vec) -> impl BuildStorage { last_update: Default::default(), } } + +pub fn parachain_id(para_id: u32) -> impl BuildStorage { + staging_parachain_info::GenesisConfig:: { + _config: Default::default(), + parachain_id: para_id.into(), + } +} diff --git a/runtime/integration-tests/src/generic/utils/xcm.rs b/runtime/integration-tests/src/generic/utils/xcm.rs index 97c788071b..9f6b4d7f07 100644 --- a/runtime/integration-tests/src/generic/utils/xcm.rs +++ b/runtime/integration-tests/src/generic/utils/xcm.rs @@ -1,69 +1,107 @@ -use frame_support::{assert_ok, traits::OriginTrait}; +use cfg_primitives::AccountId; +use cfg_types::tokens::{AssetMetadata, CrossChainTransferability, CustomMetadata}; +use frame_support::{assert_ok, dispatch::RawOrigin}; use polkadot_parachain_primitives::primitives::Id; use staging_xcm::{ prelude::XCM_VERSION, - v4::{Junction, Location}, + v4::{Junction::*, Location}, + VersionedLocation, }; use crate::generic::{ config::Runtime, env::{Blocks, Env}, - envs::fudge_env::{handle::FudgeHandle, FudgeEnv, FudgeRelayRuntime, FudgeSupport}, + envs::fudge_env::{ + handle::{PARA_ID, SIBLING_ID}, + FudgeEnv, FudgeSupport, RelayRuntime, + }, + utils::currency::default_metadata, }; -pub fn setup_xcm(env: &mut FudgeEnv) { +pub fn enable_relay_to_para_communication(env: &mut FudgeEnv) { + env.relay_state_mut(|| { + assert_ok!(pallet_xcm::Pallet::>::force_xcm_version( + RawOrigin::Root.into(), + Box::new(Location::new(0, Parachain(PARA_ID))), + XCM_VERSION, + )); + }); +} + +pub fn enable_para_to_relay_communication(env: &mut FudgeEnv) { env.parachain_state_mut(|| { - // Set the XCM version used when sending XCM messages to sibling. assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new( - 1, - Junction::Parachain(T::FudgeHandle::SIBLING_ID), - )), + RawOrigin::Root.into(), + Box::new(Location::parent()), XCM_VERSION, )); }); +} - env.sibling_state_mut(|| { - // Set the XCM version used when sending XCM messages to parachain. +pub fn enable_para_to_sibling_communication(env: &mut FudgeEnv) { + env.parachain_state_mut(|| { assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new( - 1, - Junction::Parachain(T::FudgeHandle::PARA_ID), - )), + RawOrigin::Root.into(), + Box::new(Location::new(1, Parachain(SIBLING_ID))), XCM_VERSION, )); }); env.relay_state_mut(|| { - assert_ok!(polkadot_runtime_parachains::hrmp::Pallet::< - FudgeRelayRuntime, - >::force_open_hrmp_channel( - as frame_system::Config>::RuntimeOrigin::root(), - Id::from(T::FudgeHandle::PARA_ID), - Id::from(T::FudgeHandle::SIBLING_ID), - 10, - 1024, - )); - - assert_ok!(polkadot_runtime_parachains::hrmp::Pallet::< - FudgeRelayRuntime, - >::force_open_hrmp_channel( - as frame_system::Config>::RuntimeOrigin::root(), - Id::from(T::FudgeHandle::SIBLING_ID), - Id::from(T::FudgeHandle::PARA_ID), - 10, - 1024, - )); + // Enable para -> sibling comunication though relay + assert_ok!( + polkadot_runtime_parachains::hrmp::Pallet::>::force_open_hrmp_channel( + RawOrigin::Root.into(), + Id::from(PARA_ID), + Id::from(SIBLING_ID), + 10, + 1024, + ) + ); - assert_ok!(polkadot_runtime_parachains::hrmp::Pallet::< - FudgeRelayRuntime, - >::force_process_hrmp_open( - as frame_system::Config>::RuntimeOrigin::root(), - 2, - )); + assert_ok!( + polkadot_runtime_parachains::hrmp::Pallet::>::force_process_hrmp_open( + RawOrigin::Root.into(), + 1 + ) + ); }); env.pass(Blocks::ByNumber(1)); } + +pub fn account_location( + parents: u8, + para_id: Option, + account_id: AccountId, +) -> Box { + let account = AccountId32 { + network: None, + id: account_id.into(), + }; + + Box::new(VersionedLocation::V4(match para_id { + Some(para_id) => Location::new(parents, [Parachain(para_id), account]), + None => Location::new(parents, account), + })) +} + +pub fn transferable_custom() -> CustomMetadata { + CustomMetadata { + transferability: CrossChainTransferability::xcm_with_fees(0), + ..Default::default() + } +} + +pub fn transferable_metadata(origin_para_id: Option) -> AssetMetadata { + let location = match origin_para_id { + Some(para_id) => Location::new(1, Parachain(para_id)), + None => Location::parent(), + }; + + AssetMetadata { + location: Some(VersionedLocation::V4(location)), + additional: transferable_custom(), + ..default_metadata() + } +} diff --git a/runtime/integration-tests/src/utils/mod.rs b/runtime/integration-tests/src/utils/mod.rs index f4f23fffaa..3ee465c7a6 100644 --- a/runtime/integration-tests/src/utils/mod.rs +++ b/runtime/integration-tests/src/utils/mod.rs @@ -12,3 +12,73 @@ pub mod accounts; pub mod logs; + +pub mod orml_asset_registry { + // orml_asset_registry has remove the reexport of all pallet stuff, + // we reexport it again here + pub use orml_asset_registry::module::*; +} + +pub mod approx { + use std::fmt; + + use cfg_primitives::Balance; + + #[derive(Clone)] + pub struct Approximation { + value: Balance, + offset: Balance, + is_positive: bool, + } + + impl PartialEq for Balance { + fn eq(&self, ap: &Approximation) -> bool { + match ap.is_positive { + true => *self <= ap.value && *self + ap.offset >= ap.value, + false => *self >= ap.value && *self - ap.offset <= ap.value, + } + } + } + + impl fmt::Debug for Approximation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (from, to) = match self.is_positive { + true => (self.value - self.offset, self.value), + false => (self.value, self.value + self.offset), + }; + + write!(f, "Approximation: [{}, {}]", from, to) + } + } + + /// Allow to compare `Balance` values with approximated values: + pub trait Approximate { + fn approx(&self, variation: f64) -> Approximation; + } + + impl Approximate for Balance { + fn approx(&self, variation: f64) -> Approximation { + let offset = match variation >= 0.0 { + true => (*self as f64 * variation) as Balance, + false => (*self as f64 * -variation) as Balance, + }; + + Approximation { + value: *self, + offset, + is_positive: variation >= 0.0, + } + } + } + + #[test] + fn approximations() { + assert_eq!(1000u128, 996.approx(-0.01)); + assert_eq!(1000u128, 1004.approx(0.01)); + assert_eq!(1000u128, 1500.approx(0.5)); + + assert_ne!(1000u128, 996.approx(0.01)); + assert_ne!(1000u128, 1004.approx(-0.01)); + assert_ne!(1000u128, 1500.approx(0.1)); + } +}