diff --git a/Cargo.lock b/Cargo.lock index 75c8d1e6a1..162174ebf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1642,7 +1642,6 @@ dependencies = [ "frame-support", "frame-system", "pallet-collective", - "pallet-membership", "parity-scale-codec", "scale-info", "serde", @@ -8226,22 +8225,16 @@ dependencies = [ name = "pallet-liquidity-pools" version = "0.0.1" dependencies = [ + "cfg-mocks", "cfg-primitives", "cfg-traits", "cfg-types", "cfg-utils", - "ethabi", - "fp-self-contained", - "frame-benchmarking", "frame-support", "frame-system", "hex", "orml-tokens", "orml-traits", - "pallet-balances", - "pallet-ethereum", - "pallet-timestamp", - "pallet-uniques", "parity-scale-codec", "scale-info", "sp-core", @@ -8249,7 +8242,6 @@ dependencies = [ "sp-runtime", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=release-polkadot-v1.7.2)", "staging-xcm", - "xcm-primitives", ] [[package]] diff --git a/libs/mocks/src/try_convert.rs b/libs/mocks/src/converter.rs similarity index 64% rename from libs/mocks/src/try_convert.rs rename to libs/mocks/src/converter.rs index 99fc901812..4882151717 100644 --- a/libs/mocks/src/try_convert.rs +++ b/libs/mocks/src/converter.rs @@ -3,14 +3,12 @@ pub mod pallet { use cfg_traits::TryConvert; use frame_support::pallet_prelude::*; use mock_builder::{execute_call_instance, register_call_instance}; + use sp_runtime::traits::Convert; #[pallet::config] pub trait Config: frame_system::Config { type From; - type To; - - type Error; } #[pallet::pallet] @@ -20,16 +18,26 @@ pub mod pallet { type CallIds, I: 'static = ()> = StorageMap<_, _, String, mock_builder::CallId>; impl, I: 'static> Pallet { - pub fn mock_try_convert(f: impl Fn(T::From) -> Result + 'static) { + pub fn mock_try_convert(f: impl Fn(T::From) -> Result + 'static) { + register_call_instance!(f); + } + + pub fn mock_convert(f: impl Fn(T::From) -> T::To + 'static) { register_call_instance!(f); } } impl, I: 'static> TryConvert for Pallet { - type Error = T::Error; + type Error = DispatchError; fn try_convert(from: T::From) -> Result { execute_call_instance!(from) } } + + impl, I: 'static> Convert for Pallet { + fn convert(from: T::From) -> T::To { + execute_call_instance!(from) + } + } } diff --git a/libs/mocks/src/foreign_investment.rs b/libs/mocks/src/foreign_investment.rs new file mode 100644 index 0000000000..8dd0471caa --- /dev/null +++ b/libs/mocks/src/foreign_investment.rs @@ -0,0 +1,159 @@ +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use cfg_traits::investments::ForeignInvestment; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Amount; + type TrancheAmount; + type CurrencyId; + type InvestmentId; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type CallIds = StorageMap<_, _, String, mock_builder::CallId>; + + impl Pallet { + pub fn mock_increase_foreign_investment( + f: impl Fn(&T::AccountId, T::InvestmentId, T::Amount, T::CurrencyId) -> DispatchResult + + 'static, + ) { + register_call!(move |(a, b, c, d)| f(a, b, c, d)); + } + + pub fn mock_decrease_foreign_investment( + f: impl Fn(&T::AccountId, T::InvestmentId, T::Amount, T::CurrencyId) -> DispatchResult + + 'static, + ) { + register_call!(move |(a, b, c, d)| f(a, b, c, d)); + } + + pub fn mock_increase_foreign_redemption( + f: impl Fn( + &T::AccountId, + T::InvestmentId, + T::TrancheAmount, + T::CurrencyId, + ) -> DispatchResult + + 'static, + ) { + register_call!(move |(a, b, c, d)| f(a, b, c, d)); + } + + pub fn mock_decrease_foreign_redemption( + f: impl Fn( + &T::AccountId, + T::InvestmentId, + T::TrancheAmount, + T::CurrencyId, + ) -> DispatchResult + + 'static, + ) { + register_call!(move |(a, b, c, d)| f(a, b, c, d)); + } + + pub fn mock_collect_foreign_investment( + f: impl Fn(&T::AccountId, T::InvestmentId, T::CurrencyId) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } + + pub fn mock_collect_foreign_redemption( + f: impl Fn(&T::AccountId, T::InvestmentId, T::CurrencyId) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } + + pub fn mock_investment( + f: impl Fn(&T::AccountId, T::InvestmentId) -> Result + 'static, + ) { + register_call!(move |(a, b)| f(a, b)); + } + + pub fn mock_redemption( + f: impl Fn(&T::AccountId, T::InvestmentId) -> Result + + 'static, + ) { + register_call!(move |(a, b)| f(a, b)); + } + } + + impl ForeignInvestment for Pallet { + type Amount = T::Amount; + type CurrencyId = T::CurrencyId; + type Error = DispatchError; + type InvestmentId = T::InvestmentId; + type TrancheAmount = T::TrancheAmount; + + fn increase_foreign_investment( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::Amount, + d: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c, d)) + } + + fn decrease_foreign_investment( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::Amount, + d: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c, d)) + } + + fn increase_foreign_redemption( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::TrancheAmount, + d: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c, d)) + } + + fn decrease_foreign_redemption( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::TrancheAmount, + d: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c, d)) + } + + fn collect_foreign_investment( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c)) + } + + fn collect_foreign_redemption( + a: &T::AccountId, + b: Self::InvestmentId, + c: Self::CurrencyId, + ) -> DispatchResult { + execute_call!((a, b, c)) + } + + fn investment( + a: &T::AccountId, + b: Self::InvestmentId, + ) -> Result { + execute_call!((a, b)) + } + + fn redemption( + a: &T::AccountId, + b: Self::InvestmentId, + ) -> Result { + execute_call!((a, b)) + } + } +} diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index eec8873049..de52496021 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -1,11 +1,14 @@ pub mod asset_registry; pub mod change_guard; +pub mod converter; pub mod currency_conversion; pub mod data; pub mod fees; +pub mod foreign_investment; pub mod investment; pub mod liquidity_pools; pub mod liquidity_pools_gateway_routers; +pub mod outbound_queue; pub mod pay_fee; pub mod permissions; pub mod pools; @@ -14,7 +17,6 @@ pub mod rewards; pub mod status_notification; pub mod time; pub mod token_swaps; -pub mod try_convert; pub mod value_provider; pub mod write_off_policy; @@ -33,7 +35,6 @@ pub use rewards::pallet as pallet_mock_rewards; pub use status_notification::pallet as pallet_mock_status_notification; pub use time::pallet as pallet_mock_time; pub use token_swaps::pallet as pallet_mock_token_swaps; -pub use try_convert::pallet as pallet_mock_try_convert; pub use value_provider::pallet as pallet_mock_value_provider; pub use write_off_policy::pallet as pallet_mock_write_off_policy; diff --git a/libs/mocks/src/outbound_queue.rs b/libs/mocks/src/outbound_queue.rs new file mode 100644 index 0000000000..986f4acd55 --- /dev/null +++ b/libs/mocks/src/outbound_queue.rs @@ -0,0 +1,37 @@ +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use cfg_traits::liquidity_pools::OutboundQueue; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Sender; + type Destination; + type Message; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + type CallIds = StorageMap<_, _, String, mock_builder::CallId>; + + impl Pallet { + pub fn mock_submit( + f: impl Fn(T::Sender, T::Destination, T::Message) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b, c)| f(a, b, c)); + } + } + + impl OutboundQueue for Pallet { + type Destination = T::Destination; + type Message = T::Message; + type Sender = T::Sender; + + fn submit(a: Self::Sender, b: Self::Destination, c: Self::Message) -> DispatchResult { + execute_call!((a, b, c)) + } + } +} diff --git a/libs/primitives/src/lib.rs b/libs/primitives/src/lib.rs index bc9f85db03..0d463ccad5 100644 --- a/libs/primitives/src/lib.rs +++ b/libs/primitives/src/lib.rs @@ -263,6 +263,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]; + /// The index of the root OpenGov track pub const TRACK_INDEX_ROOT: u16 = 0; /// The index of the whitelisted caller OpenGov track @@ -293,7 +296,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; } } @@ -305,7 +308,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/domain_address.rs b/libs/types/src/domain_address.rs index dc450b683a..f3a19eb094 100644 --- a/libs/types/src/domain_address.rs +++ b/libs/types/src/domain_address.rs @@ -15,7 +15,7 @@ use cfg_utils::{decode_be_bytes, vec_to_fixed_array}; use frame_support::pallet_prelude::RuntimeDebug; use parity_scale_codec::{Decode, Encode, Input, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::traits::{AccountIdConversion, Convert}; +use sp_runtime::traits::AccountIdConversion; use sp_std::{vec, vec::Vec}; use crate::EVMChainId; @@ -63,12 +63,12 @@ impl Codec for Domain { } } -impl Convert for Domain -where - AccountId: Encode + Decode, -{ - fn convert(domain: Domain) -> AccountId { - DomainLocator { domain }.into_account_truncating() +impl Domain { + pub fn into_account(&self) -> AccountId { + DomainLocator { + domain: self.clone(), + } + .into_account_truncating() } } @@ -118,3 +118,25 @@ impl DomainAddress { self.clone().into() } } + +#[cfg(test)] +mod tests { + use parity_scale_codec::{Decode, Encode}; + + use super::*; + + #[test] + fn test_domain_encode_decode() { + test_domain_identity(Domain::Centrifuge); + test_domain_identity(Domain::EVM(1284)); + test_domain_identity(Domain::EVM(1)); + } + + /// Test that (decode . encode) results in the original value + fn test_domain_identity(domain: Domain) { + let encoded = domain.encode(); + let decoded = Domain::decode(&mut encoded.as_slice()).unwrap(); + + assert_eq!(domain, decoded); + } +} diff --git a/libs/types/src/tokens.rs b/libs/types/src/tokens.rs index 1173d55835..824e6a5608 100644 --- a/libs/types/src/tokens.rs +++ b/libs/types/src/tokens.rs @@ -321,6 +321,18 @@ pub struct CustomMetadata { pub local_representation: Option, } +#[cfg(feature = "std")] +pub fn default_metadata() -> AssetMetadata { + AssetMetadata { + decimals: 0, + name: Default::default(), + symbol: Default::default(), + existential_deposit: 0, + location: None, + additional: Default::default(), + } +} + /// The Cross Chain Transferability property of an asset describes the way(s), /// if any, that said asset is cross-chain transferable. It may currently be /// transferable through Xcm, Centrifuge Liquidity Pools, or All . @@ -364,6 +376,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/pallets/investments/src/tests.rs b/pallets/investments/src/tests.rs index e0f2ca4dc6..771836b257 100644 --- a/pallets/investments/src/tests.rs +++ b/pallets/investments/src/tests.rs @@ -2510,7 +2510,8 @@ fn collecting_fully_works() { #[allow(non_snake_case)] let SINGLE_REDEEM_AMOUNT_C = 50 * CURRENCY; #[allow(non_snake_case)] - let TOTAL_REDEEM_AMOUNT = SINGLE_REDEEM_AMOUNT_A + SINGLE_REDEEM_AMOUNT_B + SINGLE_REDEEM_AMOUNT_C; + let TOTAL_REDEEM_AMOUNT = + SINGLE_REDEEM_AMOUNT_A + SINGLE_REDEEM_AMOUNT_B + SINGLE_REDEEM_AMOUNT_C; #[allow(non_snake_case)] let SINGLE_INVEST_AMOUNT_A = 50 * CURRENCY; #[allow(non_snake_case)] @@ -2518,7 +2519,8 @@ fn collecting_fully_works() { #[allow(non_snake_case)] let SINGLE_INVEST_AMOUNT_C = 50 * CURRENCY; #[allow(non_snake_case)] - let TOTAL_INVEST_AMOUNT = SINGLE_INVEST_AMOUNT_A + SINGLE_INVEST_AMOUNT_B + SINGLE_INVEST_AMOUNT_C; + let TOTAL_INVEST_AMOUNT = + SINGLE_INVEST_AMOUNT_A + SINGLE_INVEST_AMOUNT_B + SINGLE_INVEST_AMOUNT_C; #[allow(non_snake_case)] let FULL_FULFILL = FulfillmentWithPrice { of_amount: Perquintill::one(), diff --git a/pallets/liquidity-pools-gateway/src/mock.rs b/pallets/liquidity-pools-gateway/src/mock.rs index b99959e456..4f0f2c78de 100644 --- a/pallets/liquidity-pools-gateway/src/mock.rs +++ b/pallets/liquidity-pools-gateway/src/mock.rs @@ -1,13 +1,10 @@ -use cfg_mocks::{ - pallet_mock_liquidity_pools, pallet_mock_routers, pallet_mock_try_convert, MessageMock, - RouterMock, -}; +use cfg_mocks::{pallet_mock_liquidity_pools, pallet_mock_routers, MessageMock, RouterMock}; use cfg_primitives::OutboundMessageNonce; use cfg_types::domain_address::DomainAddress; use frame_support::derive_impl; use frame_system::EnsureRoot; use sp_core::{crypto::AccountId32, ConstU128, H256}; -use sp_runtime::{traits::IdentityLookup, BuildStorage, DispatchError}; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; use crate::{pallet as pallet_liquidity_pools_gateway, EnsureLocal}; @@ -26,7 +23,7 @@ frame_support::construct_runtime!( Balances: pallet_balances, MockLiquidityPools: pallet_mock_liquidity_pools, MockRouters: pallet_mock_routers, - MockOriginRecovery: pallet_mock_try_convert, + MockOriginRecovery: cfg_mocks::converter::pallet, LiquidityPoolsGateway: pallet_liquidity_pools_gateway, } ); @@ -55,8 +52,7 @@ impl pallet_mock_liquidity_pools::Config for Runtime { impl pallet_mock_routers::Config for Runtime {} -impl pallet_mock_try_convert::Config for Runtime { - type Error = DispatchError; +impl cfg_mocks::converter::pallet::Config for Runtime { type From = (Vec, Vec); type To = DomainAddress; } diff --git a/pallets/liquidity-pools/Cargo.toml b/pallets/liquidity-pools/Cargo.toml index 1bbbad918b..a5c8dfbf01 100644 --- a/pallets/liquidity-pools/Cargo.toml +++ b/pallets/liquidity-pools/Cargo.toml @@ -13,24 +13,16 @@ documentation.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -ethabi = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } hex = { workspace = true } orml-traits = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } - -# Substrate crates -sp-core = { workspace = true } - -# Optional dependencies for benchmarking -frame-benchmarking = { workspace = true, optional = true } -orml-tokens = { workspace = true, optional = true } -pallet-balances = { workspace = true, optional = true } -pallet-uniques = { workspace = true, optional = true } +staging-xcm = { workspace = true } # Our custom pallets cfg-primitives = { workspace = true } @@ -38,27 +30,16 @@ cfg-traits = { workspace = true } cfg-types = { workspace = true } cfg-utils = { workspace = true } -# Polkadot -staging-xcm = { workspace = true } - -fp-self-contained = { workspace = true } -pallet-ethereum = { workspace = true } -xcm-primitives = { workspace = true } - [dev-dependencies] -hex = { workspace = true, default-features = true } - -# Substrate crates & pallets -pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true } -pallet-uniques = { workspace = true } -sp-core = { workspace = true } +cfg-mocks = { workspace = true, default-features = true } +orml-tokens = { workspace = true, default-features = true } sp-io = { workspace = true } [features] default = ["std"] std = [ "parity-scale-codec/std", + "cfg-primitives/std", "cfg-types/std", "cfg-traits/std", "cfg-utils/std", @@ -66,22 +47,11 @@ std = [ "frame-system/std", "sp-std/std", "sp-runtime/std", - "orml-tokens/std", "orml-traits/std", - "pallet-balances/std", "staging-xcm/std", - "pallet-ethereum/std", - "xcm-primitives/std", - "ethabi/std", - "pallet-uniques/std", - "cfg-primitives/std", - "frame-benchmarking/std", "scale-info/std", - "fp-self-contained/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "orml-tokens/runtime-benchmarks", "cfg-primitives/runtime-benchmarks", "cfg-traits/runtime-benchmarks", @@ -89,9 +59,8 @@ runtime-benchmarks = [ "cfg-utils/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-ethereum/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "xcm-primitives/runtime-benchmarks", + "cfg-mocks/runtime-benchmarks", ] try-runtime = [ "cfg-primitives/try-runtime", @@ -101,8 +70,6 @@ try-runtime = [ "cfg-types/try-runtime", "cfg-utils/try-runtime", "frame-system/try-runtime", - "pallet-ethereum/try-runtime", - "pallet-balances/try-runtime", - "fp-self-contained/try-runtime", "sp-runtime/try-runtime", + "cfg-mocks/try-runtime", ] diff --git a/pallets/liquidity-pools/src/contract.rs b/pallets/liquidity-pools/src/contract.rs index 852b6e1fc8..e69de29bb2 100644 --- a/pallets/liquidity-pools/src/contract.rs +++ b/pallets/liquidity-pools/src/contract.rs @@ -1,63 +0,0 @@ -// Copyright 2021 Centrifuge Foundation (centrifuge.io). -// This file is part of Centrifuge chain project. - -// Centrifuge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version (see http://www.gnu.org/licenses). - -// Centrifuge is distributed in the hope that it will be useful, -// 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 ethabi::{Bytes, Contract}; -use sp_std::{vec, vec::Vec}; - -/// The solidity LiquidityPools' XCMRouter handle function name. -static HANDLE_FUNCTION: &str = "handle"; - -/// Return the encoded contract call, i.e, -/// LiquidityPoolsXcmRouter::handle(encoded_msg). -pub fn encoded_contract_call(encoded_msg: Vec) -> Bytes { - let contract = xcm_router_contract(); - let encoded_contract_call = contract - .function(HANDLE_FUNCTION) - .expect("Known at compilation time") - .encode_input(&[ethabi::Token::Bytes(encoded_msg)]) - .expect("Known at compilation time"); - - encoded_contract_call -} - -/// The LiquidityPoolsXcmRouter Abi as in ethabi::Contract. -/// Note: We only concern ourselves with the `handle` function of the contract -/// since that's all we need to build the calls for remote EVM execution. -pub fn xcm_router_contract() -> Contract { - use sp_std::collections::btree_map::BTreeMap; - - let mut functions = BTreeMap::new(); - #[allow(deprecated)] - functions.insert( - "handle".into(), - vec![ethabi::Function { - name: HANDLE_FUNCTION.into(), - inputs: vec![ethabi::Param { - name: "message".into(), - kind: ethabi::ParamType::Bytes, - internal_type: None, - }], - outputs: vec![], - constant: Some(false), - state_mutability: Default::default(), - }], - ); - - ethabi::Contract { - constructor: None, - functions, - events: Default::default(), - errors: Default::default(), - receive: false, - fallback: false, - } -} diff --git a/pallets/liquidity-pools/src/hooks.rs b/pallets/liquidity-pools/src/hooks.rs index fcf43a495c..50421af6ce 100644 --- a/pallets/liquidity-pools/src/hooks.rs +++ b/pallets/liquidity-pools/src/hooks.rs @@ -15,7 +15,7 @@ use cfg_traits::{ investments::TrancheCurrency, liquidity_pools::OutboundQueue, StatusNotificationHook, }; use cfg_types::{ - domain_address::{Domain, DomainAddress}, + domain_address::DomainAddress, investments::{ExecutedForeignCollect, ExecutedForeignDecreaseInvest}, }; use frame_support::{ @@ -26,7 +26,7 @@ use frame_support::{ transactional, }; use sp_core::Get; -use sp_runtime::{traits::Convert, DispatchError, DispatchResult}; +use sp_runtime::{DispatchError, DispatchResult}; use sp_std::marker::PhantomData; use crate::{pallet::Config, Message, MessageOf, Pallet}; @@ -141,7 +141,7 @@ where T::Tokens::transfer( investment_id.clone().into(), &investor, - &Domain::convert(domain_address.domain()), + &domain_address.domain().into_account(), status.amount_tranche_tokens_payout, Preservation::Expendable, )?; diff --git a/pallets/liquidity-pools/src/inbound.rs b/pallets/liquidity-pools/src/inbound.rs index 3386fddff4..185d04cbd4 100644 --- a/pallets/liquidity-pools/src/inbound.rs +++ b/pallets/liquidity-pools/src/inbound.rs @@ -81,7 +81,7 @@ where T::Tokens::transfer( invest_id.into(), - &Domain::convert(sending_domain), + &sending_domain.into_account(), &local_representation_of_receiver, amount, Preservation::Expendable, @@ -206,7 +206,7 @@ where // origination domain T::Tokens::transfer( invest_id.clone().into(), - &Domain::convert(sending_domain.domain()), + &sending_domain.domain().into_account(), &investor, amount, Preservation::Expendable, @@ -252,7 +252,7 @@ where T::Tokens::transfer( invest_id.clone().into(), &investor, - &Domain::convert(destination.domain()), + &destination.domain().into_account(), tranche_tokens_payout, Preservation::Expendable, )?; diff --git a/pallets/liquidity-pools/src/lib.rs b/pallets/liquidity-pools/src/lib.rs index 2d1a0dea54..76d8c827b6 100644 --- a/pallets/liquidity-pools/src/lib.rs +++ b/pallets/liquidity-pools/src/lib.rs @@ -41,7 +41,10 @@ #![cfg_attr(not(feature = "std"), no_std)] use core::convert::TryFrom; -use cfg_traits::liquidity_pools::{InboundQueue, OutboundQueue}; +use cfg_traits::{ + liquidity_pools::{InboundQueue, OutboundQueue}, + PreConditions, +}; use cfg_types::{ domain_address::{Domain, DomainAddress}, tokens::GeneralCurrencyIndex, @@ -56,8 +59,6 @@ use frame_support::{ }; use orml_traits::asset_registry::{self, Inspect as _}; pub use pallet::*; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert}, FixedPointNumber, SaturatedConversion, @@ -73,25 +74,16 @@ use staging_xcm::{ pub mod defensive_weights; mod message; -pub use message::*; - -mod routers; -pub use routers::*; - -mod contract; -pub use contract::*; +pub use message::Message; pub mod hooks; mod inbound; -/// The Parachains that Centrifuge Liquidity Pools support. -#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Debug))] -pub enum ParachainId { - /// Moonbeam - It may be Moonbeam on Polkadot, Moonriver on Kusama, or - /// Moonbase on a testnet. - Moonbeam, -} +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; // Type aliases pub type MessageOf = Message< @@ -102,8 +94,6 @@ pub type MessageOf = Message< ::BalanceRatio, >; -pub type CurrencyIdOf = ::CurrencyId; - pub type GeneralCurrencyIndexType = u128; pub type GeneralCurrencyIndexOf = @@ -113,8 +103,7 @@ pub type GeneralCurrencyIndexOf = pub mod pallet { use cfg_traits::{ investments::{ForeignInvestment, TrancheCurrency}, - CurrencyInspect, Permissions, PoolInspect, PreConditions, Seconds, TimeAsSecs, - TrancheTokenPrice, + CurrencyInspect, Permissions, PoolInspect, Seconds, TimeAsSecs, TrancheTokenPrice, }; use cfg_types::{ permissions::{PermissionScope, PoolRole, Role}, @@ -172,23 +161,19 @@ pub mod pallet { + FixedPointNumber + TypeInfo; - /// The origin allowed to make admin-like changes, such calling - /// `set_domain_router`. - type AdminOrigin: EnsureOrigin; - /// The source of truth for pool inspection operations such as its /// existence, the corresponding tranche token or the investment /// currency. type PoolInspect: PoolInspect< Self::AccountId, - CurrencyIdOf, + Self::CurrencyId, PoolId = Self::PoolId, TrancheId = Self::TrancheId, >; type TrancheTokenPrice: TrancheTokenPrice< Self::AccountId, - CurrencyIdOf, + Self::CurrencyId, BalanceRatio = Self::BalanceRatio, PoolId = Self::PoolId, TrancheId = Self::TrancheId, @@ -198,7 +183,7 @@ pub mod pallet { /// The source of truth for investment permissions. type Permission: Permissions< Self::AccountId, - Scope = PermissionScope>, + Scope = PermissionScope, Role = Role, Error = DispatchError, >; @@ -210,15 +195,11 @@ pub mod pallet { /// The type for handling transfers, burning and minting of /// multi-assets. type Tokens: Mutate - + Inspect< - Self::AccountId, - AssetId = CurrencyIdOf, - Balance = ::Balance, - >; + + Inspect; /// The currency type of investments. type TrancheCurrency: TrancheCurrency - + Into> + + Into + Clone; /// Enables investing and redeeming into investment classes with foreign @@ -227,7 +208,7 @@ pub mod pallet { Self::AccountId, Amount = Self::Balance, TrancheAmount = Self::Balance, - CurrencyId = CurrencyIdOf, + CurrencyId = Self::CurrencyId, Error = DispatchError, InvestmentId = ::TrancheCurrency, >; @@ -235,7 +216,7 @@ pub mod pallet { /// The source of truth for the transferability of assets via the /// LiquidityPools feature. type AssetRegistry: asset_registry::Inspect< - AssetId = CurrencyIdOf, + AssetId = Self::CurrencyId, Balance = ::Balance, CustomMetadata = CustomMetadata, >; @@ -251,15 +232,11 @@ pub mod pallet { + TryInto, Error = DispatchError> + TryFrom, Error = DispatchError> // Enables checking whether currency is tranche token - + CurrencyInspect>; + + CurrencyInspect; /// The converter from a DomainAddress to a Substrate AccountId. type DomainAddressToAccountId: Convert; - /// The converter from a Domain and 32 byte array to Substrate - /// AccountId. - type DomainAccountToAccountId: Convert<(Domain, [u8; 32]), Self::AccountId>; - /// The converter from a Domain and a 32 byte array to DomainAddress. type DomainAccountToDomainAddress: Convert<(Domain, [u8; 32]), DomainAddress>; @@ -274,13 +251,13 @@ pub mod pallet { #[pallet::constant] type GeneralCurrencyPrefix: Get<[u8; 12]>; - #[pallet::constant] /// The type for paying the transaction fees for the dispatch of /// `Executed*` and `ScheduleUpgrade` messages. /// /// NOTE: We need to make sure to collect the appropriate amount /// beforehand as part of receiving the corresponding investment /// message. + #[pallet::constant] type TreasuryAccount: Get; type PreTransferFilter: PreConditions< @@ -355,7 +332,7 @@ pub mod pallet { ::AccountId: From<[u8; 32]> + Into<[u8; 32]>, { /// Add a pool to a given domain - #[pallet::weight(< T as Config >::WeightInfo::add_pool())] + #[pallet::weight(T::WeightInfo::add_pool())] #[pallet::call_index(2)] pub fn add_pool( origin: OriginFor, @@ -383,7 +360,7 @@ pub mod pallet { } /// Add a tranche to a given domain - #[pallet::weight(< T as Config >::WeightInfo::add_tranche())] + #[pallet::weight(T::WeightInfo::add_tranche())] #[pallet::call_index(3)] pub fn add_tranche( origin: OriginFor, @@ -393,11 +370,6 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin.clone())?; - ensure!( - T::PoolInspect::tranche_exists(pool_id, tranche_id), - Error::::TrancheNotFound - ); - ensure!( T::Permission::has( PermissionScope::Pool(pool_id), @@ -440,13 +412,13 @@ pub mod pallet { /// domain, this call origin can be permissionless. /// /// The `currency_id` parameter is necessary for the EVM side. - #[pallet::weight(< T as Config >::WeightInfo::update_token_price())] + #[pallet::weight(T::WeightInfo::update_token_price())] #[pallet::call_index(4)] pub fn update_token_price( origin: OriginFor, pool_id: T::PoolId, tranche_id: T::TrancheId, - currency_id: CurrencyIdOf, + currency_id: T::CurrencyId, destination: Domain, ) -> DispatchResult { let who = ensure_signed(origin.clone())?; @@ -484,7 +456,7 @@ pub mod pallet { } /// Update a member - #[pallet::weight(< T as Config >::WeightInfo::update_member())] + #[pallet::weight(T::WeightInfo::update_member())] #[pallet::call_index(5)] pub fn update_member( origin: OriginFor, @@ -539,14 +511,14 @@ pub mod pallet { /// /// NOTE: The transferring account is not kept alive as we allow its /// death. - #[pallet::weight(< T as Config >::WeightInfo::transfer())] + #[pallet::weight(T::WeightInfo::transfer())] #[pallet::call_index(6)] pub fn transfer_tranche_tokens( origin: OriginFor, pool_id: T::PoolId, tranche_id: T::TrancheId, domain_address: DomainAddress, - amount: ::Balance, + amount: T::Balance, ) -> DispatchResult { let who = ensure_signed(origin.clone())?; @@ -573,7 +545,7 @@ pub mod pallet { T::Tokens::transfer( invest_id.into(), &who, - &Domain::convert(domain_address.domain()), + &domain_address.domain().into_account(), amount, // NOTE: Here, we allow death Preservation::Expendable, @@ -587,10 +559,7 @@ pub mod pallet { tranche_id, amount, domain: domain_address.domain(), - sender: who - .encode() - .try_into() - .map_err(|_| DispatchError::Other("Conversion to 32 bytes failed"))?, + sender: who.into(), receiver: domain_address.address(), }, )?; @@ -604,19 +573,19 @@ pub mod pallet { /// /// NOTE: The transferring account is not kept alive as we allow its /// death. - #[pallet::weight(< T as Config >::WeightInfo::transfer())] + #[pallet::weight(T::WeightInfo::transfer())] #[pallet::call_index(7)] pub fn transfer( origin: OriginFor, - currency_id: CurrencyIdOf, + currency_id: T::CurrencyId, receiver: DomainAddress, - amount: ::Balance, + amount: T::Balance, ) -> DispatchResult { let who = ensure_signed(origin.clone())?; ensure!(!amount.is_zero(), Error::::InvalidTransferAmount); ensure!( - !CurrencyIdOf::::is_tranche_token(currency_id), + !T::CurrencyId::is_tranche_token(currency_id), Error::::InvalidTransferCurrency ); let currency = Self::try_get_general_index(currency_id)?; @@ -665,10 +634,7 @@ pub mod pallet { Message::Transfer { amount, currency, - sender: who - .encode() - .try_into() - .map_err(|_| DispatchError::Other("Conversion to 32 bytes failed"))?, + sender: who.into(), receiver: receiver.address(), }, )?; @@ -680,7 +646,7 @@ pub mod pallet { /// from the given currency. #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] #[pallet::call_index(8)] - pub fn add_currency(origin: OriginFor, currency_id: CurrencyIdOf) -> DispatchResult { + pub fn add_currency(origin: OriginFor, currency_id: T::CurrencyId) -> DispatchResult { let who = ensure_signed(origin)?; let currency = Self::try_get_general_index(currency_id)?; @@ -709,7 +675,7 @@ pub mod pallet { pub fn allow_investment_currency( origin: OriginFor, pool_id: T::PoolId, - currency_id: CurrencyIdOf, + currency_id: T::CurrencyId, ) -> DispatchResult { // TODO(future): In the future, should be permissioned by trait which // does not exist yet. @@ -754,7 +720,7 @@ pub mod pallet { } /// Schedule an upgrade of an EVM-based liquidity pool contract instance - #[pallet::weight(::WeightInfo::cancel_upgrade())] + #[pallet::weight(T::WeightInfo::cancel_upgrade())] #[pallet::call_index(11)] pub fn cancel_upgrade( origin: OriginFor, @@ -775,7 +741,7 @@ pub mod pallet { /// NOTE: Pulls the metadata from the `AssetRegistry` and thus requires /// the pool admin to have updated the tranche tokens metadata there /// beforehand. - #[pallet::weight(::WeightInfo::update_tranche_token_metadata())] + #[pallet::weight(T::WeightInfo::update_tranche_token_metadata())] #[pallet::call_index(12)] pub fn update_tranche_token_metadata( origin: OriginFor, @@ -815,7 +781,7 @@ pub mod pallet { pub fn disallow_investment_currency( origin: OriginFor, pool_id: T::PoolId, - currency_id: CurrencyIdOf, + currency_id: T::CurrencyId, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -848,13 +814,13 @@ pub mod pallet { /// Requires the currency to be registered in the `AssetRegistry`. /// /// NOTE: Reverse operation of `try_get_currency_id`. - pub fn try_get_general_index(currency: CurrencyIdOf) -> Result { + pub fn try_get_general_index(currency: T::CurrencyId) -> Result { ensure!( T::AssetRegistry::metadata(¤cy).is_some(), Error::::AssetNotFound ); - let general_index: GeneralCurrencyIndexOf = CurrencyIdOf::::try_into(currency)?; + let general_index: GeneralCurrencyIndexOf = T::CurrencyId::try_into(currency)?; Ok(general_index.index) } @@ -866,8 +832,8 @@ pub mod pallet { /// NOTE: Reverse operation of `try_get_general_index`. pub fn try_get_currency_id( index: GeneralCurrencyIndexOf, - ) -> Result, DispatchError> { - let currency = CurrencyIdOf::::try_from(index)?; + ) -> Result { + let currency = T::CurrencyId::try_from(index)?; ensure!( T::AssetRegistry::metadata(¤cy).is_some(), Error::::AssetNotFound @@ -882,7 +848,7 @@ pub mod pallet { /// /// Requires the currency to be registered in the `AssetRegistry`. pub fn try_get_wrapped_token( - currency_id: &CurrencyIdOf, + currency_id: &T::CurrencyId, ) -> Result { let meta = T::AssetRegistry::metadata(currency_id).ok_or(Error::::AssetNotFound)?; ensure!( @@ -937,7 +903,7 @@ pub mod pallet { /// Performs multiple checks for the provided currency and returns its /// general index and the EVM chain ID associated with it. pub fn validate_investment_currency( - currency_id: CurrencyIdOf, + currency_id: T::CurrencyId, ) -> Result<(u128, EVMChainId), DispatchError> { // Ensure the currency is enabled as pool_currency let metadata = @@ -954,6 +920,11 @@ pub mod pallet { Ok((currency, chain_id)) } + + fn domain_account_to_account_id(domain_account: (Domain, [u8; 32])) -> T::AccountId { + let domain_address = T::DomainAccountToDomainAddress::convert(domain_account); + T::DomainAddressToAccountId::convert(domain_address) + } } impl InboundQueue for Pallet @@ -1000,7 +971,7 @@ pub mod pallet { } => Self::handle_increase_invest_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), amount, ), @@ -1013,7 +984,7 @@ pub mod pallet { } => Self::handle_decrease_invest_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), amount, ), @@ -1026,7 +997,7 @@ pub mod pallet { } => Self::handle_increase_redeem_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), amount, currency.into(), sender, @@ -1040,7 +1011,7 @@ pub mod pallet { } => Self::handle_decrease_redeem_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), amount, currency.into(), sender, @@ -1053,7 +1024,7 @@ pub mod pallet { } => Self::handle_collect_investment( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), ), Message::CollectRedeem { @@ -1064,7 +1035,7 @@ pub mod pallet { } => Self::handle_collect_redemption( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), ), Message::CancelInvestOrder { @@ -1075,7 +1046,7 @@ pub mod pallet { } => Self::handle_cancel_invest_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), ), Message::CancelRedeemOrder { @@ -1086,7 +1057,7 @@ pub mod pallet { } => Self::handle_cancel_redeem_order( pool_id, tranche_id, - T::DomainAccountToAccountId::convert((sender.domain(), investor)), + Self::domain_account_to_account_id((sender.domain(), investor)), currency.into(), sender, ), @@ -1097,25 +1068,3 @@ pub mod pallet { } } } - -#[cfg(test)] -mod tests { - use parity_scale_codec::{Decode, Encode}; - - use crate::Domain; - - #[test] - fn test_domain_encode_decode() { - test_domain_identity(Domain::Centrifuge); - test_domain_identity(Domain::EVM(1284)); - test_domain_identity(Domain::EVM(1)); - } - - /// Test that decode . encode results in the original value - fn test_domain_identity(domain: Domain) { - let encoded = domain.encode(); - let decoded: Domain = Domain::decode(&mut encoded.as_slice()).expect(""); - - assert_eq!(domain, decoded); - } -} diff --git a/pallets/liquidity-pools/src/mock.rs b/pallets/liquidity-pools/src/mock.rs new file mode 100644 index 0000000000..aaff960192 --- /dev/null +++ b/pallets/liquidity-pools/src/mock.rs @@ -0,0 +1,141 @@ +use cfg_primitives::{PoolId, TrancheId}; +use cfg_traits::Millis; +use cfg_types::{ + domain_address::{Domain, DomainAddress}, + permissions::PermissionScope, + tokens::{AssetStringLimit, CurrencyId, CustomMetadata, TrancheCurrency}, +}; +use frame_support::derive_impl; +use orml_traits::parameter_type_with_key; +use sp_runtime::{traits::IdentityLookup, AccountId32, DispatchResult, FixedU64}; + +use crate::pallet as pallet_liquidity_pools; + +pub type Balance = u128; +pub type AccountId = AccountId32; +pub type Ratio = FixedU64; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Time: cfg_mocks::time::pallet, + Permissions: cfg_mocks::permissions::pallet, + Pools: cfg_mocks::pools::pallet, + AssetRegistry: cfg_mocks::asset_registry::pallet, + ForeignInvestment: cfg_mocks::foreign_investment::pallet, + Gateway: cfg_mocks::outbound_queue::pallet, + DomainAddressToAccountId: cfg_mocks::converter::pallet::, + DomainAccountToDomainAddress: cfg_mocks::converter::pallet::, + TransferFilter: cfg_mocks::pre_conditions::pallet, + Tokens: orml_tokens, + LiquidityPools: pallet_liquidity_pools, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Block = frame_system::mocking::MockBlock; + type Lookup = IdentityLookup; +} + +impl cfg_mocks::time::pallet::Config for Runtime { + type Moment = Millis; +} + +impl cfg_mocks::permissions::pallet::Config for Runtime { + type Scope = PermissionScope; +} + +impl cfg_mocks::pools::pallet::Config for Runtime { + type Balance = Balance; + type BalanceRatio = Ratio; + type CurrencyId = CurrencyId; + type PoolId = PoolId; + type TrancheCurrency = TrancheCurrency; + type TrancheId = TrancheId; +} + +impl cfg_mocks::asset_registry::pallet::Config for Runtime { + type AssetId = CurrencyId; + type Balance = Balance; + type CustomMetadata = CustomMetadata; + type StringLimit = AssetStringLimit; +} + +impl cfg_mocks::foreign_investment::pallet::Config for Runtime { + type Amount = Balance; + type CurrencyId = CurrencyId; + type InvestmentId = TrancheCurrency; + type TrancheAmount = Balance; +} + +impl cfg_mocks::outbound_queue::pallet::Config for Runtime { + type Destination = Domain; + type Message = crate::MessageOf; + type Sender = AccountId; +} + +impl cfg_mocks::converter::pallet::Config for Runtime { + type From = DomainAddress; + type To = AccountId; +} + +impl cfg_mocks::converter::pallet::Config for Runtime { + type From = (Domain, [u8; 32]); + type To = DomainAddress; +} + +impl cfg_mocks::pre_conditions::pallet::Config for Runtime { + type Conditions = (AccountId, DomainAddress, CurrencyId); + type Result = DispatchResult; +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: CurrencyId| -> Balance { + Default::default() + }; +} + +impl orml_tokens::Config for Runtime { + type Amount = i64; + type Balance = Balance; + type CurrencyHooks = (); + type CurrencyId = CurrencyId; + type DustRemovalWhitelist = frame_support::traits::Nothing; + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +frame_support::parameter_types! { + pub CurrencyPrefix: [u8; 12] = [1; 12]; + pub TreasuryAccount: AccountId = [2; 32].into(); +} + +impl pallet_liquidity_pools::Config for Runtime { + type AssetRegistry = AssetRegistry; + type Balance = Balance; + type BalanceRatio = Ratio; + type CurrencyId = CurrencyId; + type DomainAccountToDomainAddress = DomainAccountToDomainAddress; + type DomainAddressToAccountId = DomainAddressToAccountId; + type ForeignInvestment = ForeignInvestment; + type GeneralCurrencyPrefix = CurrencyPrefix; + type OutboundQueue = Gateway; + type Permission = Permissions; + type PoolId = PoolId; + type PoolInspect = Pools; + type PreTransferFilter = TransferFilter; + type RuntimeEvent = RuntimeEvent; + type Time = Time; + type Tokens = Tokens; + type TrancheCurrency = TrancheCurrency; + type TrancheId = TrancheId; + type TrancheTokenPrice = Pools; + type TreasuryAccount = TreasuryAccount; + type WeightInfo = (); +} diff --git a/pallets/liquidity-pools/src/routers.rs b/pallets/liquidity-pools/src/routers.rs deleted file mode 100644 index 15d16e4dec..0000000000 --- a/pallets/liquidity-pools/src/routers.rs +++ /dev/null @@ -1,35 +0,0 @@ -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; -use sp_core::H160; -use sp_runtime::{traits::ConstU32, BoundedVec}; -use sp_std::boxed::Box; -use staging_xcm::VersionedLocation; - -#[allow(clippy::derive_partial_eq_without_eq)] // XcmDomain does not impl Eq -#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Debug))] -pub enum Router { - // An XCM-based router - Xcm(XcmDomain), -} - -/// XcmDomain gathers all the required fields to build and send remote -/// calls to a specific XCM-based Domain. -#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct XcmDomain { - /// the xcm multilocation of the domain - pub location: Box, - /// The ethereum_xcm::Call::transact call index on a given domain. - /// It should contain the pallet index + the `transact` call index, to which - /// we will append the eth_tx param. You can obtain this value by building - /// an ethereum_xcm::transact call with Polkadot JS on the target chain. - pub ethereum_xcm_transact_call_index: - BoundedVec>, - /// The LiquidityPoolsXcmRouter contract address on a given domain - pub contract_address: H160, - /// The currency in which execution fees will be paid on - pub fee_currency: CurrencyId, - /// The max gas_limit we want to propose for a remote evm execution - pub max_gas_limit: u64, -} diff --git a/pallets/liquidity-pools/src/tests.rs b/pallets/liquidity-pools/src/tests.rs new file mode 100644 index 0000000000..439fe35362 --- /dev/null +++ b/pallets/liquidity-pools/src/tests.rs @@ -0,0 +1,632 @@ +use cfg_primitives::{PoolId, TrancheId}; +use cfg_traits::{liquidity_pools::InboundQueue, Millis}; +use cfg_types::{ + domain_address::DomainAddress, + permissions::{PermissionScope, PoolRole, Role}, + tokens::{AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata}, +}; +use cfg_utils::vec_to_fixed_array; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungibles::{Inspect as _, Mutate as _}, + PalletInfo as _, + }, +}; +use sp_runtime::{DispatchError, TokenError}; +use staging_xcm::{ + v4::{Junction::*, Location, NetworkId}, + VersionedLocation, +}; + +use crate::{mock::*, Error, GeneralCurrencyIndexOf, Message}; + +const CHAIN_ID: u64 = 1; +const ALICE: AccountId = AccountId::new([0; 32]); +const CONTRACT_ACCOUNT: [u8; 20] = [1; 20]; +const CONTRACT_ACCOUNT_ID: AccountId = AccountId::new([1; 32]); +const EVM_ADDRESS: DomainAddress = DomainAddress::EVM(CHAIN_ID, CONTRACT_ACCOUNT); +const AMOUNT: Balance = 100; +const CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1); +const POOL_ID: PoolId = 1; +const TRANCHE_ID: TrancheId = [1; 16]; +const NOW: Millis = 0; +const NAME: &[u8] = b"Token name"; +const SYMBOL: &[u8] = b"Token symbol"; +const DECIMALS: u8 = 6; +const TRANCHE_CURRENCY: CurrencyId = CurrencyId::Tranche(POOL_ID, TRANCHE_ID); + +mod util { + use super::*; + + pub fn default_metadata() -> AssetMetadata { + AssetMetadata { + decimals: DECIMALS as u32, + name: Vec::from(NAME).try_into().unwrap(), + symbol: Vec::from(SYMBOL).try_into().unwrap(), + ..cfg_types::tokens::default_metadata() + } + } + + pub fn transferable_metadata() -> AssetMetadata { + AssetMetadata { + additional: CustomMetadata { + transferability: CrossChainTransferability::LiquidityPools, + ..Default::default() + }, + ..default_metadata() + } + } + + pub fn wrapped_transferable_metadata() -> AssetMetadata { + let pallet_index = PalletInfo::index::(); + AssetMetadata { + 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, + }, + ], + ))), + ..transferable_metadata() + } + } + + pub fn currency_index(currency_id: CurrencyId) -> u128 { + GeneralCurrencyIndexOf::::try_from(currency_id) + .unwrap() + .index + } +} + +mod transfer { + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + TransferFilter::mock_check(|_| Ok(())); + Tokens::mint_into(CURRENCY_ID, &ALICE, AMOUNT).unwrap(); + Gateway::mock_submit(|sender, destination, msg| { + assert_eq!(sender, ALICE); + assert_eq!(destination, EVM_ADDRESS.domain()); + assert_eq!( + msg, + Message::Transfer { + currency: util::currency_index(CURRENCY_ID), + sender: ALICE.into(), + receiver: EVM_ADDRESS.address(), + amount: AMOUNT + } + ); + Ok(()) + }); + + assert_ok!(LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CurrencyId::ForeignAsset(1), + EVM_ADDRESS, + AMOUNT + )); + + assert_eq!(Tokens::total_issuance(CURRENCY_ID), 0); + }) + } + + mod erroring_out_when { + use super::*; + + #[test] + fn with_zero_balance() { + System::externalities().execute_with(|| { + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + 0 + ), + Error::::InvalidTransferAmount, + ); + }) + } + + #[test] + fn with_tranche_currency() { + System::externalities().execute_with(|| { + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CurrencyId::Tranche(42, [0; 16]), + EVM_ADDRESS, + AMOUNT + ), + Error::::InvalidTransferCurrency, + ); + }) + } + + #[test] + fn with_no_metadata() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| None); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::AssetNotFound, + ); + }) + } + + #[test] + fn with_unsupported_token() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::default_metadata())); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CurrencyId::Native, + EVM_ADDRESS, + AMOUNT + ), + TokenError::Unsupported, + ); + }) + } + + #[test] + fn with_no_transferible_asset() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::default_metadata())); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::AssetNotLiquidityPoolsTransferable, + ); + }) + } + + #[test] + fn with_wrong_location() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::transferable_metadata())); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::AssetNotLiquidityPoolsWrappedToken + ); + }) + } + + #[test] + fn with_wrong_domain() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + DomainAddress::Centrifuge([2; 32]), + AMOUNT + ), + Error::::InvalidDomain + ); + }) + } + + #[test] + fn without_satisfy_filter() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + TransferFilter::mock_check(|_| Err(DispatchError::Other("Err"))); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + AMOUNT + ), + DispatchError::Other("Err"), + ); + }) + } + + #[test] + fn without_sufficient_balance() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + TransferFilter::mock_check(|_| Ok(())); + + assert_noop!( + LiquidityPools::transfer( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::BalanceTooLow + ); + }) + } + } +} + +mod transfer_tranche_tokens { + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + DomainAddressToAccountId::mock_convert(|_| CONTRACT_ACCOUNT_ID); + Time::mock_now(|| NOW); + Permissions::mock_has(move |scope, who, role| { + assert_eq!(who, CONTRACT_ACCOUNT_ID); + assert!(matches!(scope, PermissionScope::Pool(POOL_ID))); + assert!(matches!( + role, + Role::PoolRole(PoolRole::TrancheInvestor(TRANCHE_ID, NOW)) + )); + true + }); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| true); + TransferFilter::mock_check(|_| Ok(())); + Tokens::mint_into(TRANCHE_CURRENCY, &ALICE, AMOUNT).unwrap(); + Gateway::mock_submit(|sender, destination, msg| { + assert_eq!(sender, ALICE); + assert_eq!(destination, EVM_ADDRESS.domain()); + assert_eq!( + msg, + Message::TransferTrancheTokens { + pool_id: POOL_ID, + tranche_id: TRANCHE_ID, + sender: ALICE.into(), + domain: EVM_ADDRESS.domain(), + receiver: EVM_ADDRESS.address(), + amount: AMOUNT + } + ); + Ok(()) + }); + + assert_ok!(LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + AMOUNT + )); + + let destination = EVM_ADDRESS.domain().into_account(); + assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &ALICE), 0); + assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &destination), AMOUNT); + }) + } + + mod erroring_out_when { + use super::*; + + #[test] + fn with_zero_balance() { + System::externalities().execute_with(|| { + assert_noop!( + LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + 0 + ), + Error::::InvalidTransferAmount, + ); + }) + } + + #[test] + fn with_wrong_permissions() { + System::externalities().execute_with(|| { + DomainAddressToAccountId::mock_convert(|_| CONTRACT_ACCOUNT_ID); + Time::mock_now(|| NOW); + Permissions::mock_has(|_, _, _| false); + + assert_noop!( + LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::UnauthorizedTransfer, + ); + }) + } + + #[test] + fn with_wrong_pool() { + System::externalities().execute_with(|| { + DomainAddressToAccountId::mock_convert(|_| CONTRACT_ACCOUNT_ID); + Time::mock_now(|| NOW); + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| false); + + assert_noop!( + LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::PoolNotFound, + ); + }) + } + + #[test] + fn with_wrong_tranche() { + System::externalities().execute_with(|| { + DomainAddressToAccountId::mock_convert(|_| CONTRACT_ACCOUNT_ID); + Time::mock_now(|| NOW); + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| false); + + assert_noop!( + LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + AMOUNT + ), + Error::::TrancheNotFound, + ); + }) + } + + #[test] + fn without_satisfy_filter() { + System::externalities().execute_with(|| { + DomainAddressToAccountId::mock_convert(|_| CONTRACT_ACCOUNT_ID); + Time::mock_now(|| NOW); + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| true); + TransferFilter::mock_check(|_| Err(DispatchError::Other("Err"))); + + assert_noop!( + LiquidityPools::transfer_tranche_tokens( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS, + AMOUNT + ), + DispatchError::Other("Err"), + ); + }) + } + } +} + +mod add_pool { + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |scope, who, role| { + assert_eq!(who, ALICE); + assert!(matches!(scope, PermissionScope::Pool(POOL_ID))); + assert!(matches!(role, Role::PoolRole(PoolRole::PoolAdmin))); + true + }); + Pools::mock_pool_exists(|_| true); + Gateway::mock_submit(|sender, destination, msg| { + assert_eq!(sender, ALICE); + assert_eq!(destination, EVM_ADDRESS.domain()); + assert_eq!(msg, Message::AddPool { pool_id: POOL_ID }); + Ok(()) + }); + + assert_ok!(LiquidityPools::add_pool( + RuntimeOrigin::signed(ALICE), + POOL_ID, + EVM_ADDRESS.domain(), + )); + }) + } + + mod erroring_out_when { + use super::*; + + #[test] + fn with_wrong_pool() { + System::externalities().execute_with(|| { + Pools::mock_pool_exists(|_| false); + + assert_noop!( + LiquidityPools::add_pool( + RuntimeOrigin::signed(ALICE), + POOL_ID, + EVM_ADDRESS.domain(), + ), + Error::::PoolNotFound + ); + }) + } + + #[test] + fn with_wrong_permissions() { + System::externalities().execute_with(|| { + Pools::mock_pool_exists(|_| true); + Permissions::mock_has(move |_, _, _| false); + + assert_noop!( + LiquidityPools::add_pool( + RuntimeOrigin::signed(ALICE), + POOL_ID, + EVM_ADDRESS.domain(), + ), + Error::::NotPoolAdmin + ); + }) + } + } +} + +mod add_tranche { + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |scope, who, role| { + assert_eq!(who, ALICE); + assert!(matches!(scope, PermissionScope::Pool(POOL_ID))); + assert!(matches!(role, Role::PoolRole(PoolRole::PoolAdmin))); + true + }); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| true); + AssetRegistry::mock_metadata(|_| Some(util::default_metadata())); + Gateway::mock_submit(|sender, destination, msg| { + assert_eq!(sender, ALICE); + assert_eq!(destination, EVM_ADDRESS.domain()); + assert_eq!( + msg, + Message::AddTranche { + pool_id: POOL_ID, + tranche_id: TRANCHE_ID, + token_name: vec_to_fixed_array(NAME), + token_symbol: vec_to_fixed_array(SYMBOL), + decimals: DECIMALS, + restriction_set: 1 + } + ); + Ok(()) + }); + + assert_ok!(LiquidityPools::add_tranche( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS.domain(), + )); + }) + } + + mod erroring_out_when { + use super::*; + + #[test] + fn with_wrong_permissions() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |_, _, _| false); + + assert_noop!( + LiquidityPools::add_tranche( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS.domain(), + ), + Error::::NotPoolAdmin + ); + }) + } + + #[test] + fn with_wrong_pool() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| false); + + assert_noop!( + LiquidityPools::add_tranche( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS.domain(), + ), + Error::::PoolNotFound + ); + }) + } + + #[test] + fn with_wrong_tranche() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| false); + + assert_noop!( + LiquidityPools::add_tranche( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS.domain(), + ), + Error::::TrancheNotFound, + ); + }) + } + + #[test] + fn with_no_metadata() { + System::externalities().execute_with(|| { + Permissions::mock_has(move |_, _, _| true); + Pools::mock_pool_exists(|_| true); + Pools::mock_tranche_exists(|_, _| true); + AssetRegistry::mock_metadata(|_| None); + + assert_noop!( + LiquidityPools::add_tranche( + RuntimeOrigin::signed(ALICE), + POOL_ID, + TRANCHE_ID, + EVM_ADDRESS.domain(), + ), + Error::::TrancheMetadataNotFound, + ); + }) + } + } +} + +#[test] +fn receiving_output_message() { + System::externalities().execute_with(|| { + let msg = Message::AddPool { pool_id: 123 }; + + assert_noop!( + LiquidityPools::submit(EVM_ADDRESS, msg), + Error::::InvalidIncomingMessage, + ); + }) +} diff --git a/pallets/pool-system/src/pool_types.rs b/pallets/pool-system/src/pool_types.rs index 6cf2830f50..0594d15ab2 100644 --- a/pallets/pool-system/src/pool_types.rs +++ b/pallets/pool-system/src/pool_types.rs @@ -209,7 +209,8 @@ impl< TrancheId, PoolId, MaxTranches, - > where + > +where Balance: FixedPointOperand + BaseArithmetic + Unsigned + From + sp_arithmetic::MultiplyRational, CurrencyId: Copy, diff --git a/pallets/pool-system/src/tranches.rs b/pallets/pool-system/src/tranches.rs index 9f8e00d46f..9b7011ca13 100644 --- a/pallets/pool-system/src/tranches.rs +++ b/pallets/pool-system/src/tranches.rs @@ -2908,10 +2908,11 @@ pub mod test { mod rebalance { use super::*; - const TOTAL_ASSETS: Balance = DEBT_RES - + RESERVE_RES + DEBT_NONRES_1 - + RESERVE_NONRES_1 - + DEBT_NONRES_2 + RESERVE_NONRES_2; + const TOTAL_ASSETS: Balance = + DEBT_RES + + RESERVE_RES + DEBT_NONRES_1 + + RESERVE_NONRES_1 + + DEBT_NONRES_2 + RESERVE_NONRES_2; const RATIO_NONRES_1: Balance = DEBT_NONRES_1 + RESERVE_NONRES_1; const RATIO_NONRES_2: Balance = DEBT_NONRES_2 + RESERVE_NONRES_2; const DEFAULT_NAV: Balance = 1_234_567_890; diff --git a/runtime/altair/src/lib.rs b/runtime/altair/src/lib.rs index 0751293ef7..3dd4f14b58 100644 --- a/runtime/altair/src/lib.rs +++ b/runtime/altair/src/lib.rs @@ -1800,12 +1800,10 @@ parameter_types! { } impl pallet_liquidity_pools::Config for Runtime { - type AdminOrigin = EnsureRoot; type AssetRegistry = OrmlAssetRegistry; type Balance = Balance; type BalanceRatio = Ratio; type CurrencyId = CurrencyId; - type DomainAccountToAccountId = AccountConverter; type DomainAccountToDomainAddress = AccountConverter; type DomainAddressToAccountId = AccountConverter; type ForeignInvestment = ForeignInvestments; diff --git a/runtime/altair/src/migrations.rs b/runtime/altair/src/migrations.rs index 738078d873..f18ae39552 100644 --- a/runtime/altair/src/migrations.rs +++ b/runtime/altair/src/migrations.rs @@ -57,6 +57,8 @@ pub type UpgradeAltair1100 = ( cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, pallet_identity::migration::versioned::V0ToV1, pallet_uniques::migration::MigrateV0ToV1, + pallet_collator_selection::migration::v2::MigrationToV2, + runtime_common::migrations::loans::AddWithLinearPricing, // Initialize OpenGov TechnicalCommittee runtime_common::migrations::technical_comittee::InitMigration, runtime_common::migrations::increase_storage_version::Migration, diff --git a/runtime/altair/src/xcm.rs b/runtime/altair/src/xcm.rs index 0b5bedb2bb..379a0606e2 100644 --- a/runtime/altair/src/xcm.rs +++ b/runtime/altair/src/xcm.rs @@ -10,7 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::parachains; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_types::tokens::CurrencyId; use frame_support::{ parameter_types, @@ -22,13 +22,11 @@ use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::MultiNativeAsset; use pallet_xcm::XcmPassthrough; use runtime_common::{ - origins::gov::types::{EnsureRootOr, HalfOfCouncil}, 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::{ @@ -86,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/lib.rs b/runtime/centrifuge/src/lib.rs index 0ff9c81eef..7576b85552 100644 --- a/runtime/centrifuge/src/lib.rs +++ b/runtime/centrifuge/src/lib.rs @@ -1880,12 +1880,10 @@ parameter_types! { } impl pallet_liquidity_pools::Config for Runtime { - type AdminOrigin = EnsureRootOr; type AssetRegistry = OrmlAssetRegistry; type Balance = Balance; type BalanceRatio = Ratio; type CurrencyId = CurrencyId; - type DomainAccountToAccountId = AccountConverter; type DomainAccountToDomainAddress = AccountConverter; type DomainAddressToAccountId = AccountConverter; type ForeignInvestment = ForeignInvestments; diff --git a/runtime/centrifuge/src/xcm.rs b/runtime/centrifuge/src/xcm.rs index 6b13bafe20..ef74d41fe1 100644 --- a/runtime/centrifuge/src/xcm.rs +++ b/runtime/centrifuge/src/xcm.rs @@ -10,7 +10,7 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::parachains; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_traits::TryConvert; use cfg_types::{tokens::CurrencyId, EVMChainId}; use frame_support::{ @@ -23,13 +23,11 @@ use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::MultiNativeAsset; use pallet_xcm::XcmPassthrough; use runtime_common::{ - origins::gov::types::{EnsureRootOr, HalfOfCouncil}, 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::{ @@ -87,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/account_conversion.rs b/runtime/common/src/account_conversion.rs index 9e896c9db5..ded709ec11 100644 --- a/runtime/common/src/account_conversion.rs +++ b/runtime/common/src/account_conversion.rs @@ -13,7 +13,7 @@ use cfg_primitives::AccountId; use cfg_types::domain_address::{Domain, DomainAddress}; use pallet_evm::AddressMapping; -use sp_core::{crypto::AccountId32, Get, H160}; +use sp_core::{Get, H160}; use sp_runtime::traits::Convert; use staging_xcm::v4::{Junction::AccountKey20, Location, NetworkId::Ethereum}; use staging_xcm_executor::traits::ConvertLocation; @@ -61,10 +61,15 @@ impl AccountConverter { } } - pub fn into_account_id(address: H160) -> AccountId { + pub fn evm_address_to_account(address: H160) -> AccountId { let chain_id = pallet_evm_chain_id::Pallet::::get(); Self::convert_evm_address(chain_id, address.0) } + + pub fn domain_account_to_account(domain: Domain, account_id: AccountId) -> AccountId { + let domain_address = Self::convert((domain, account_id.into())); + Self::convert(domain_address) + } } impl Convert for AccountConverter { @@ -89,20 +94,6 @@ impl Convert<(Domain, [u8; 32]), DomainAddress> for AccountConverter { } } -impl Convert<(Domain, [u8; 32]), AccountId32> for AccountConverter { - fn convert((domain, account): (Domain, [u8; 32])) -> AccountId32 { - match domain { - Domain::Centrifuge => AccountId32::new(account), - // EVM AccountId20 addresses are right-padded to 32 bytes - Domain::EVM(chain_id) => { - let mut bytes20 = [0; 20]; - bytes20.copy_from_slice(&account[..20]); - Self::convert_evm_address(chain_id, bytes20) - } - } - } -} - // A type that use AccountConverter to carry along with it the Runtime type and // offer an `AddressMapping` implementation. // Required by `pallet_evm` @@ -110,7 +101,7 @@ pub struct RuntimeAccountConverter(sp_std::marker::PhantomData); impl AddressMapping for RuntimeAccountConverter { fn into_account_id(address: H160) -> AccountId { - AccountConverter::into_account_id::(address) + AccountConverter::evm_address_to_account::(address) } } diff --git a/runtime/common/src/gateway.rs b/runtime/common/src/gateway.rs index 4129eec96c..5b75077ac7 100644 --- a/runtime/common/src/gateway.rs +++ b/runtime/common/src/gateway.rs @@ -25,5 +25,5 @@ pub fn get_gateway_account>::as_ref(&sender_account)[0..20]); - AccountConverter::into_account_id::(truncated_sender_account) + AccountConverter::evm_address_to_account::(truncated_sender_account) } diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index ca46d02f6b..2f39864ed3 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -39,7 +39,6 @@ pub mod fees; pub mod gateway; pub mod migrations; pub mod oracle; -pub mod origins; pub mod pool; pub mod remarks; pub mod transfer_filter; @@ -264,7 +263,8 @@ pub mod asset_registry { impl< Origin: Into, Origin>> + From>, DefaultEnsureOrigin: EnsureOrigin, - > EnsureOriginWithArg> for AuthorityOrigin + > EnsureOriginWithArg> + for AuthorityOrigin { type Success = (); diff --git a/runtime/common/src/xcm.rs b/runtime/common/src/xcm.rs index 56ab50a56d..aa82c339de 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 { @@ -260,10 +272,7 @@ pub type LocationToAccountId = ( #[cfg(test)] mod test { - use cfg_mocks::{ - pallet_mock_liquidity_pools, pallet_mock_routers, pallet_mock_try_convert, MessageMock, - RouterMock, - }; + use cfg_mocks::{pallet_mock_liquidity_pools, pallet_mock_routers, MessageMock, RouterMock}; use cfg_primitives::OutboundMessageNonce; use frame_support::{assert_ok, derive_impl, traits::EnsureOrigin}; use frame_system::EnsureRoot; @@ -276,18 +285,14 @@ mod test { type AccountId = u64; - pub fn new_test_ext() -> sp_io::TestExternalities { - System::externalities() - } - // For testing the pallet, we construct a mock runtime. frame_support::construct_runtime!( pub enum Runtime { System: frame_system, Gateway: pallet_liquidity_pools_gateway, MockLP: pallet_mock_liquidity_pools, - MockParaAsEvmChain: pallet_mock_try_convert::, - MockOriginRecovery: pallet_mock_try_convert::, + MockParaAsEvmChain: cfg_mocks::converter::pallet::, + MockOriginRecovery: cfg_mocks::converter::pallet::, } ); @@ -296,14 +301,12 @@ mod test { type Block = frame_system::mocking::MockBlock; } - impl pallet_mock_try_convert::Config for Runtime { - type Error = (); + impl cfg_mocks::converter::pallet::Config for Runtime { type From = ParaId; type To = EVMChainId; } - impl pallet_mock_try_convert::Config for Runtime { - type Error = DispatchError; + impl cfg_mocks::converter::pallet::Config for Runtime { type From = (Vec, Vec); type To = DomainAddress; } @@ -336,7 +339,7 @@ mod test { #[test] fn lp_instance_relayer_converts_correctly() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { let expected_address = DomainAddress::EVM(RELAYER_EVM_ID, RELAYER_ADDRESS); assert_ok!(Gateway::add_relayer( @@ -375,7 +378,7 @@ mod test { #[test] fn lp_instance_relayer_fails_with_wrong_location() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { let expected_address = DomainAddress::EVM(RELAYER_EVM_ID, RELAYER_ADDRESS); assert_ok!(Gateway::add_relayer( @@ -403,7 +406,7 @@ mod test { #[test] fn lp_instance_relayer_fails_if_relayer_not_set() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { MockParaAsEvmChain::mock_try_convert(|from| { assert_eq!(from, RELAYER_PARA_ID); Ok(RELAYER_EVM_ID) @@ -433,7 +436,7 @@ mod test { #[test] fn lp_instance_relayer_fails_if_para_to_evm_fails() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { let expected_address = DomainAddress::EVM(RELAYER_EVM_ID, RELAYER_ADDRESS); assert_ok!(Gateway::add_relayer( @@ -443,7 +446,7 @@ mod test { MockParaAsEvmChain::mock_try_convert(|from| { assert_eq!(from, RELAYER_PARA_ID); - Err(()) + Err(DispatchError::Other("")) }); let location = Location::new( @@ -470,7 +473,7 @@ mod test { #[test] fn lp_instance_relayer_fails_if_wrong_para() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { let expected_address = DomainAddress::EVM(RELAYER_EVM_ID, RELAYER_ADDRESS); assert_ok!(Gateway::add_relayer( @@ -480,7 +483,7 @@ mod test { MockParaAsEvmChain::mock_try_convert(|from| { assert_eq!(from, 1); - Err(()) + Err(DispatchError::Other("")) }); let location = Location::new( @@ -507,7 +510,7 @@ mod test { #[test] fn lp_instance_relayer_fails_if_wrong_address() { - new_test_ext().execute_with(|| { + System::externalities().execute_with(|| { let expected_address = DomainAddress::EVM(RELAYER_EVM_ID, RELAYER_ADDRESS); assert_ok!(Gateway::add_relayer( diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index ffd028af74..d9eae11b8f 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -1900,12 +1900,10 @@ parameter_types! { } impl pallet_liquidity_pools::Config for Runtime { - type AdminOrigin = EnsureRoot; type AssetRegistry = OrmlAssetRegistry; type Balance = Balance; type BalanceRatio = Ratio; type CurrencyId = CurrencyId; - type DomainAccountToAccountId = AccountConverter; type DomainAccountToDomainAddress = AccountConverter; type DomainAddressToAccountId = AccountConverter; type ForeignInvestment = ForeignInvestments; diff --git a/runtime/development/src/xcm.rs b/runtime/development/src/xcm.rs index 2c7a769a53..ac260246f0 100644 --- a/runtime/development/src/xcm.rs +++ b/runtime/development/src/xcm.rs @@ -9,7 +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; +use cfg_primitives::types::{EnsureRootOr, HalfOfCouncil}; use cfg_traits::TryConvert; use cfg_types::{tokens::CurrencyId, EVMChainId}; use frame_support::{ @@ -22,13 +22,11 @@ use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::MultiNativeAsset; use pallet_xcm::XcmPassthrough; use runtime_common::{ - origins::gov::types::{EnsureRootOr, HalfOfCouncil}, 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::{ @@ -93,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..093cb4f842 --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/assets.rs @@ -0,0 +1,52 @@ +use cfg_types::tokens::{default_metadata, 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::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..4cbed8080f --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/currency_conversions.rs @@ -0,0 +1,100 @@ +use cfg_types::tokens::{default_metadata, 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::{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 b7c9e6b93c..b9da600a28 100644 --- a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs @@ -3,21 +3,18 @@ use cfg_primitives::{ }; use cfg_traits::{ investments::{Investment, OrderManager, TrancheCurrency}, - liquidity_pools::{Codec, InboundQueue, OutboundQueue}, + liquidity_pools::{InboundQueue, OutboundQueue}, IdentityCurrencyConversion, Permissions, PoolInspect, PoolMutate, Seconds, }; 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, @@ -33,56 +30,33 @@ use liquidity_pools_gateway_routers::{ use orml_traits::MultiCurrency; use pallet_investments::CollectOutcome; use pallet_liquidity_pools::Message; -use pallet_liquidity_pools_gateway::{Call as LiquidityPoolsGatewayCall, GatewayOrigin}; +use pallet_liquidity_pools_gateway::Call as LiquidityPoolsGatewayCall; 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::{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 +65,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 +105,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 +123,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,70 +145,35 @@ 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. token_name: BoundedVec::< u8, ::StringLimit, - >::try_from("A highly advanced tranche".as_bytes().to_vec()) + >::try_from( + "A highly advanced tranche".as_bytes().to_vec() + ) .expect("Can create BoundedVec for token name"), token_symbol: BoundedVec::< u8, @@ -281,376 +181,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, @@ -658,7138 +451,3623 @@ 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(), + &DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(), + 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()], - ); - - 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(), - ); - } + // Verify order id is 0 + assert_eq!( + pallet_investments::Pallet::::redeem_order_id(investment_id::( + pool_id, + default_tranche_id::(pool_id) + )), + 0 + ); + } - amount_foreign_denominated - } + /// 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() + }, + }; - pub fn get_council_members() -> Vec { - vec![Keyring::Alice, Keyring::Bob, Keyring::Charlie] - } + assert_ok!(orml_asset_registry::Pallet::::register_asset( + ::RuntimeOrigin::root(), + meta, + Some(USDT_CURRENCY_ID) + )); } - mod add_allow_upgrade { - use cfg_types::tokens::LiquidityPoolsWrappedToken; + /// 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(); - use super::*; + if enable_lp_transferability { + enable_liquidity_pool_transferability::(foreign_currency); + } - #[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(), + 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(), ); + } - setup_test(&mut env); + amount_foreign_denominated + } - env.parachain_state_mut(|| { - let pool_id = POOL_ID; + pub fn get_council_members() -> Vec { + vec![Keyring::Alice, Keyring::Bob, Keyring::Charlie] + } +} - // 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 - ); +use utils::*; - // Now create the pool - create_ausd_pool::(pool_id); +mod add_allow_upgrade { + use cfg_types::tokens::LiquidityPoolsWrappedToken; - // 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 - ); + use super::*; - // 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), - )); - }); - } + #[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(), + ); - #[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(), - ); + setup_test(&mut env); - setup_test(&mut env); + env.parachain_state_mut(|| { + // Now create the pool + let pool_id = POOL_ID; - env.parachain_state_mut(|| { - // Now create the pool - let pool_id = POOL_ID; - create_ausd_pool::(pool_id); + create_ausd_pool::(pool_id); - // Verify we can't call pallet_liquidity_pools::Pallet::::add_tranche with a - // non-existing tranche_id - let nonexistent_tranche = [71u8; 16]; + let tranche_id = default_tranche_id::(pool_id); - 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); + // 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 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 - ); + // 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 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(), + // 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, - Domain::EVM(MOONBEAM_EVM_CHAIN_ID), - )); + new_member.clone(), + DEFAULT_VALIDITY, + ), + pallet_liquidity_pools::Error::::InvestorDomainAddressNotAMember, + ); - // Edge case: Should throw if tranche exists but metadata does not exist - let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_id); + // 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), + ); - orml_asset_registry::Metadata::::remove(tranche_currency_id); + // 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)), + )); - 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 - ); - }); - } + // 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, + )); - #[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(), + // 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, ); + }); + } - 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(|| { - // Now create the pool - let pool_id = POOL_ID; + setup_test(&mut env); - create_ausd_pool::(pool_id); + env.parachain_state_mut(|| { + let currency_id = AUSD_CURRENCY_ID; + let pool_id = POOL_ID; - let tranche_id = default_tranche_id::(pool_id); + enable_liquidity_pool_transferability::(currency_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]); + create_ausd_pool::(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), - )); + 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), + )); + }); + } - // 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, - ); + #[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(), + ); - // 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), - ); + setup_test(&mut env); - // 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)), - )); + env.parachain_state_mut(|| { + let gateway_sender = ::Sender::get(); - // 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, - )); + let currency_id = AUSD_CURRENCY_ID; - // 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, - ); - }); - } + enable_liquidity_pool_transferability::(currency_id); - #[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(), + assert_eq!( + orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), + DEFAULT_BALANCE_GLMR ); - setup_test(&mut env); - - env.parachain_state_mut(|| { - let currency_id = AUSD_CURRENCY_ID; - let pool_id = POOL_ID; + assert_ok!(pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id, + )); - enable_liquidity_pool_transferability::(currency_id); + let currency_index = + pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) + .expect("can get general index for currency"); - create_ausd_pool::(pool_id); + let LiquidityPoolsWrappedToken::EVM { + address: evm_address, + .. + } = pallet_liquidity_pools::Pallet::::try_get_wrapped_token(¤cy_id) + .expect("can get wrapped token"); - 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), - )); - }); - } + let outbound_message = pallet_liquidity_pools_gateway::OutboundMessageQueue::::get( + T::OutboundMessageNonce::one(), + ) + .expect("expected outbound queue message"); - #[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_eq!( + outbound_message.2, + Message::AddCurrency { + currency: currency_index, + evm_address, + }, ); + }); + } - setup_test(&mut env); - - let (domain, sender, message) = env.parachain_state_mut(|| { - let gateway_sender = ::Sender::get(); - - let currency_id = AUSD_CURRENCY_ID; - - enable_liquidity_pool_transferability::(currency_id); + #[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_eq!( - orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), - DEFAULT_BALANCE_GLMR - ); + setup_test(&mut env); - assert_ok!(pallet_liquidity_pools::Pallet::::add_currency( + env.parachain_state_mut(|| { + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id, - )); + 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 currency_index = - pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) - .expect("can get general index for currency"); + // Should fail to add currency_id which is missing a registered + // Location + let currency_id = CurrencyId::ForeignAsset(100); - let LiquidityPoolsWrappedToken::EVM { - address: evm_address, - .. - } = pallet_liquidity_pools::Pallet::::try_get_wrapped_token(¤cy_id) - .expect("can get wrapped token"); + 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 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 - }); + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken + ); - let expected_event = - pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { - sender, - domain, - message, - nonce: T::OutboundMessageNonce::one(), - }; + // 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() + }), + )); - env.pass(Blocks::UntilEvent { - event: expected_event.clone().into(), - limit: 3, - }); + assert_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable + ); - env.check_event(expected_event) - .expect("expected RouterExecutionSuccess event"); - } + // 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 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_noop!( + pallet_liquidity_pools::Pallet::::add_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + currency_id + ), + pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable ); + }); + } - setup_test(&mut env); + #[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(), + ); - 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 - ); + setup_test(&mut env); - // 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) - )); + 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]; - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); + // Create an AUSD pool + create_ausd_pool::(pool_id); - // 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() - }), - )); + enable_liquidity_pool_transferability::(currency_id); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); + // 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() + }) + )); - // Switch transferability from XCM to None - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), + assert_ok!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, 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); + assert_noop!( + pallet_liquidity_pools::Pallet::::allow_investment_currency( + RawOrigin::Signed(Keyring::Charlie.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin + ); + }); + } - 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]; + #[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(), + ); - // Create an AUSD pool - create_ausd_pool::(pool_id); + setup_test(&mut env); - enable_liquidity_pool_transferability::(currency_id); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let currency_id = CurrencyId::ForeignAsset(42); + let ausd_currency_id = AUSD_CURRENCY_ID; - // Enable LiquidityPools transferability - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), + // 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, - 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, + ), + 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) + )); - assert_ok!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - pool_id, - currency_id, - ) - ); + // Create pool + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); - assert_noop!( - pallet_liquidity_pools::Pallet::::allow_investment_currency( - RawOrigin::Signed(Keyring::Charlie.into()).into(), - pool_id, - currency_id, - ), - pallet_liquidity_pools::Error::::NotPoolAdmin - ); - }); - } + enable_liquidity_pool_transferability::(ausd_currency_id); - #[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(), + // 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 ); - setup_test(&mut env); + // 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 + ); - 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 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 + ); + }); + } - // 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 - ); + #[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(), + ); - // 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) - )); + setup_test(&mut env); - // Create pool - create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); + 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]; - enable_liquidity_pool_transferability::(ausd_currency_id); + // Create an AUSD pool + create_ausd_pool::(pool_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 - ); + enable_liquidity_pool_transferability::(currency_id); - // 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(), - ); - - 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 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 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); - - 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 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, - ); - }); - } - } -} - -mod altair { - use altair_runtime::{xcm::CurrencyIdConvert, PoolPalletIndex}; - use utils::*; - - use super::*; - - pub const KSM_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(1000); - - mod utils { - use super::*; - - 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() - }, - }; - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - } - - 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() - }, - }; - - assert_ok!(orml_asset_registry::Pallet::::register_asset( + // Enable LiquidityPools transferability + assert_ok!(orml_asset_registry::Pallet::::update_asset( ::RuntimeOrigin::root(), - meta, - Some(KSM_ASSET_ID) + 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() + }) )); - } - - pub fn air(amount: Balance) -> Balance { - amount * decimals(currency_decimals::NATIVE) - } - - pub fn ksm(amount: Balance) -> Balance { - amount * decimals(currency_decimals::KSM) - } - - pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { - amount * decimals(num_decimals) - } - - pub fn air_fee() -> Balance { - fee(currency_decimals::NATIVE) - } - - // The fee associated with transferring KSM tokens - pub fn ksm_fee() -> Balance { - calc_fee(ksm_per_second()) - } - } - - mod transfers { - use super::*; - - 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); - - 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 - ); - - // 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.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(air_in_sibling, &Keyring::Bob.into()), - 0 - ); - - // 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(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()); - - // 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([altair])] - fn test_air_transfers_to_and_from_sibling() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(air(10))) - .storage(), - ); - - 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); - - // Note: This asset was registered in `transfer_air_to_sibling` - let air_in_sibling = CurrencyId::ForeignAsset(12); - - env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_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(air_in_sibling, &Keyring::Bob.into()), - bob_initial_balance - ); - - assert_ok!(orml_xtokens::Pallet::::transfer( - 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()), - )); - - // 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 - ); - }); - - env.pass(Blocks::ByNumber(3)); - - 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(), - ); - }); - } - - #[test_runtimes([altair])] - fn transfer_ausd_to_altair() { - let mut env = FudgeEnv::::default(); - - setup_xcm(&mut env); - - let alice_initial_balance = ausd(10); - let transfer_amount = ausd(7); - - env.sibling_state_mut(|| { - register_ausd::(); - - assert_ok!(orml_tokens::Pallet::::deposit( - AUSD_CURRENCY_ID, - &Keyring::Alice.into(), - alice_initial_balance - )); - - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - 0 - ); - }); - - env.parachain_state_mut(|| { - register_ausd::(); - - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - 0, - ); - }); - - 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( - 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 - ); - - // 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 - ); - }); - - env.pass(Blocks::ByNumber(3)); - - 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() - ); - }); - } - - 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), - )); - - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &Keyring::Bob.into()), - 0 - ); - }); - - 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, - ) - ); - - 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, - ) - ); - - 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 - ) - ); - }); - - env.pass(Blocks::ByNumber(2)); - - env.parachain_state(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(currency_id, &Keyring::Bob.into()), - 1991963000000 // Comes from `transfer_amount - fee(meta.decimals)` with noise - ); - }); - } - - #[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()); - - let currency_id = CurrencyId::ForeignAsset(3001); - - 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( + assert_ok!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( RawOrigin::Signed(Keyring::Bob.into()).into(), + pool_id, currency_id, - ksm(1), - Box::new( - Location::new( - 1, - Junction::AccountId32 { - id: Keyring::Bob.into(), - network: None, - } - ) - .into() - ), - WeightLimit::Limited(4_000_000_000.into()) - )); - }); - - env.pass(Blocks::ByNumber(2)); - - env.relay_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Bob.into() - ), - 999989698923 - ); - }); - } - - #[test_runtimes([altair])] - fn transfer_foreign_sibling_to_altair() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(air(10))) - .storage(), + ) ); - setup_xcm(&mut env); - - let sibling_asset_id = CurrencyId::ForeignAsset(1); - let asset_location = Location::new( - 1, - [Parachain(T::FudgeHandle::SIBLING_ID), general_key(&[0, 1])], + assert_noop!( + pallet_liquidity_pools::Pallet::::disallow_investment_currency( + RawOrigin::Signed(Keyring::Charlie.into()).into(), + pool_id, + currency_id, + ), + pallet_liquidity_pools::Error::::NotPoolAdmin ); - 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.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), - )); - }); + }); + } - env.parachain_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(sibling_asset_id) - )); - }); + #[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.sibling_state_mut(|| { - assert_ok!(pallet_balances::Pallet::::force_set_balance( - ::RuntimeOrigin::root(), - Keyring::Alice.id().into(), - transfer_amount * 2, - )); + setup_test(&mut env); - 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() - ), - WeightLimit::Limited(8_000_000_000_000.into()), - )); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let currency_id = CurrencyId::ForeignAsset(42); + let ausd_currency_id = AUSD_CURRENCY_ID; - // Confirm that Alice's balance is initial balance - amount transferred - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - transfer_amount - ); - }); + // 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 + ); - env.pass(Blocks::ByNumber(3)); + // 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) + )); - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()); + // Create pool + create_currency_pool::(pool_id, currency_id, 10_000 * decimals(12)); - // 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); - }); - } + enable_liquidity_pool_transferability::(ausd_currency_id); - #[test_runtimes([altair])] - fn transfer_wormhole_usdc_karura_to_altair() { - let mut env = FudgeEnv::::from_storage( - Default::default(), - Default::default(), - 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); + // 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 + ); - let usdc_asset_id = CurrencyId::ForeignAsset(39); - let asset_location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::SIBLING_ID), - general_key("0x02f3a00dd12f644daec907013b16eb6d14bf1c4cb4".as_bytes()), - ], + // 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 ); - 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()), - air(10) - ); - }); + }); + } - 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) - )); - }); + #[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_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()), - )); + setup_test(&mut env); - // 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 - ); - }); + 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] + )); + }); + } - 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(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Bob.into()); + setup_test(&mut env); - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 11993571); - }); - } + 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] + )); + }); } - mod asset_registry { - use super::*; + #[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(), + ); - #[test_runtimes([altair])] - fn register_air_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::kusama::altair::AIR_KEY), - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..CustomMetadata::default() - }, - }; + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + // NOTE: Default pool admin is BOB + create_ausd_pool::(pool_id); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - }); - } + // Missing tranche token should throw + let nonexistent_tranche = [71u8; 16]; - #[test_runtimes([altair])] - fn register_foreign_asset_works() { - let mut env = FudgeEnv::::default(); + 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 + ); - 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 tranche_id = default_tranche_id::(pool_id); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::ForeignAsset(42)) - )); - }); - } + // 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), + ) + ); - // Verify that registering tranche tokens is not allowed through extrinsics - #[test_runtimes([altair])] - fn register_tranche_asset_blocked() { - let mut env = FudgeEnv::::default(); + // Edge case: Should throw if tranche exists but metadata does not exist + let tranche_currency_id = CurrencyId::Tranche(pool_id, tranche_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() - }, - }; + orml_asset_registry::Metadata::::remove(tranche_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 - ); - }); - } + 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 currency_id_convert { + mod same_currencies { use super::*; - #[test_runtimes([altair])] - fn convert_air() { - let mut env = FudgeEnv::::default(); + #[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(), + ); - assert_eq!(parachains::kusama::altair::AIR_KEY.to_vec(), vec![0, 1]); + setup_test(&mut env); 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)); + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - // register air - register_air::(); + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - assert_eq!( - >::convert(air_location_inner), - Some(CurrencyId::Native), - ); + // Set permissions and execute initial investment + do_initial_increase_investment::(pool_id, amount, investor.clone(), currency_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), - ], + // Verify the order was updated to the amount + assert_eq!( + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + amount ); - assert_eq!( - >::convert(CurrencyId::Native), - Some(air_location_canonical) - ) + // 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 + )); }); } - /// 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 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), - }, - ], + #[test_runtimes([development])] + fn decrease_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), ); - env.parachain_state_mut(|| { - assert_eq!( - >::convert(tranche_multilocation), - None, - ); - }); + setup_test(&mut env); env.parachain_state_mut(|| { - assert_eq!( - >::convert(tranche_currency), - None - ) - }); - } + 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 = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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, + ); - #[test_runtimes([altair])] - fn convert_ausd() { - let mut env = FudgeEnv::::default(); + // 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, + }; - 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), - ], + // 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); - register_ausd::(); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + // Verify investment was decreased into investment account assert_eq!( - >::convert(ausd_location.clone()), - Some(AUSD_CURRENCY_ID), + 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!( - >::convert(AUSD_CURRENCY_ID), - Some(ausd_location) - ) + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, + final_amount + ); }); } - #[test_runtimes([altair])] - fn convert_ksm() { - let mut env = FudgeEnv::::default(); + #[test_runtimes([development])] + fn cancel_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - let ksm_location: Location = Location::parent().into(); + setup_test(&mut env); env.parachain_state_mut(|| { - register_ksm::(); + let pool_id = POOL_ID; + let invest_amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - assert_eq!( - >::convert(ksm_location.clone()), - Some(KSM_ASSET_ID), + // 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!( - >::convert(KSM_ASSET_ID), - Some(ksm_location) - ) - }); - } + orml_tokens::Pallet::::balance( + currency_id, + &default_investment_account::() + ), + invest_amount + ); - #[test_runtimes([altair])] - fn convert_unkown_multilocation() { - let mut env = FudgeEnv::::default(); + // 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), + }; - let unknown_location: Location = - Location::new(1, [Parachain(T::FudgeHandle::PARA_ID), general_key(&[42])]); + // 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 + ); - env.parachain_state_mut(|| { - assert!(>::convert(unknown_location).is_none()); - }); - } + enable_liquidity_pool_transferability::(currency_id); - #[test_runtimes([altair])] - fn convert_unsupported_currency() { - let mut env = FudgeEnv::::default(); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - env.parachain_state_mut(|| { + // Verify investment was entirely drained from investment account assert_eq!( - >::convert(CurrencyId::Tranche( - 0, - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - )), - None - ) + 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 + ); }); } - } -} -mod centrifuge { - use centrifuge_runtime::xcm::CurrencyIdConvert; - use utils::*; - - use super::*; + #[test_runtimes([development])] + fn collect_invest_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - mod utils { - use super::*; + setup_test(&mut env); - /// The test asset id attributed to DOT - pub const DOT_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(91); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(); + enable_liquidity_pool_transferability::(currency_id); - pub const LP_ETH_USDC: CurrencyId = CurrencyId::ForeignAsset(100_001); + // 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(); - pub const USDC: CurrencyId = CurrencyId::ForeignAsset(6); + // 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::() + )); - /// An Asset that is NOT XCM transferable - pub const NO_XCM_ASSET_ID: CurrencyId = CurrencyId::ForeignAsset(401); + // 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 + ); - /// 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) - )); - } + // 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 + )); - 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() - }, - }; + // Remove events before collect execution + let events_since_collect: Vec<_> = frame_system::Pallet::::events() + .into_iter() + .filter(|e| !events_before_collect.contains(e)) + .collect(); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(LP_ETH_USDC) - )); - } + // Verify investment was transferred to the domain locator + assert_eq!( + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator + ), + amount + ); - 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) - )); - } + // 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() + })); - /// 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() - }, - }; + // 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() + })); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); - } + // 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() + })); - /// 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() - }, - }; + let sender = ::Sender::get(); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(CurrencyId::Native) - )); + // 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() + })); + }); } - /// 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() - }, - }; + #[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(), + ); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(NO_XCM_ASSET_ID) - )); - } + setup_test(&mut env); - // The fee associated with transferring DOT tokens - pub fn dot_fee() -> Balance { - fee(10) - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let invest_amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(); + 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(); - pub fn lp_eth_usdc_fee() -> Balance { - fee(6) - } + assert!( + !pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) + ); - pub fn usdc_fee() -> Balance { - fee(6) - } + // 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(), + } + )); - pub fn dot(amount: Balance) -> Balance { - amount * decimals(10) - } + // Pre collect assertions + assert!( + pallet_investments::Pallet::::investment_requires_collect( + &investor, + default_investment_id::() + ) + ); - pub fn lp_eth_usdc(amount: Balance) -> Balance { - amount * decimals(6) - } + // 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::() + ) + ); - pub fn usdc(amount: Balance) -> Balance { - amount * decimals(6) - } + // 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() + })); - pub fn foreign(amount: Balance, num_decimals: u32) -> Balance { - amount * decimals(num_decimals) - } + let sender = ::Sender::get(); - pub fn transfer_dot_from_relay_chain(env: &mut FudgeEnv) { - let alice_initial_dot = dot(10); - let transfer_amount: Balance = dot(3); + 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(|| { - register_dot::(); + // 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!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), + pallet_investments::Pallet::::acc_active_invest_order( + default_investment_id::(), + ) + .amount, 0 ); - }); - - 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, - ) + assert_eq!( + orml_tokens::Pallet::::total_issuance(investment_currency_id), + invest_amount * 3 ); - 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, + // 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::() ) ); - 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 - ) + // 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!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() + orml_tokens::Pallet::::balance( + investment_currency_id, + &sending_domain_locator ), - 69867666991 // Comes from alice_initial_dot - transfer_amount with noise + 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.pass(Blocks::ByNumber(2)); + 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() + })); - env.parachain_state(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - 29919630000 // Comes from `transfer_amount - dot_fee()` with some noise + // 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 ); }); } - } - mod asset_registry { - use super::*; + #[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([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 = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; - 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 redemption + do_initial_increase_redemption::(pool_id, amount, investor.clone(), 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(parachains::polkadot::acala::ID), - general_key(parachains::polkadot::acala::AUSD_KEY), - ], - ))), - additional: CustomMetadata { - transferability: CrossChainTransferability::Xcm(Default::default()), - ..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(CurrencyId::ForeignAsset(42)) + // Increasing again should just bump redeeming amount + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(), + 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 )); }); } - // Verify that registering tranche tokens is not allowed through extrinsics - #[test_runtimes([centrifuge])] - fn register_tranche_asset_blocked() { - let mut env = FudgeEnv::::default(); + #[test_runtimes([development])] + fn decrease_redeem_order() { + 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 redeem_amount = 10 * decimals(12); + let decrease_amount = redeem_amount / 3; + let final_amount = redeem_amount - decrease_amount; + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(); - mod currency_id_convert { - use super::*; + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - #[test_runtimes([centrifuge])] - fn convert_cfg() { - let mut env = FudgeEnv::::default(); + // Set permissions and execute initial redemption + do_initial_increase_redemption::( + pool_id, + redeem_amount, + investor.clone(), + currency_id, + ); - assert_eq!(parachains::polkadot::centrifuge::CFG_KEY, &[0, 1]); + // 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 + ); - 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)); + // 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, + }; - register_cfg::(T::FudgeHandle::PARA_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::(), + ), + final_amount + ); + // Tokens should have been transferred from investor's wallet to domain's + // sovereign account assert_eq!( - >::convert(cfg_location_inner), - Some(CurrencyId::Native), + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &investor + ), + 0 ); - - // 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), - ], + 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!( - >::convert(CurrencyId::Native), - Some(cfg_location_canonical) - ) + 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() + })); }); } - /// 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 cancel_redeem_order() { + 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)); + let pool_id = POOL_ID; + let redeem_amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + let currency_id = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let sending_domain_locator = + DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(); - assert_eq!( - >::convert(cfg_location_inner), - Some(CurrencyId::Native), - ); + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.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), - ], + // 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!( - >::convert(CurrencyId::Native), - Some(cfg_location_canonical) - ) - }); - } + pallet_investments::Pallet::::invest_order_id(investment_id::( + pool_id, + default_tranche_id::(pool_id) + )), + 0 + ); - /// 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(); + // 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), + }; - env.parachain_state_mut(|| { - register_no_xcm_token::(); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + // Verify investment was decreased into investment account assert_eq!( - >::convert(NO_XCM_ASSET_ID), - None - ) - }); - } - - #[test_runtimes([centrifuge])] - fn convert_dot() { - let mut env = FudgeEnv::::default(); - - let dot_location: Location = Location::parent(); - - env.parachain_state_mut(|| { - register_dot::(); - + 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!( - >::convert(dot_location.clone()), - Some(DOT_ASSET_ID), + 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!( - >::convert(DOT_ASSET_ID), - Some(dot_location) - ) + pallet_investments::Pallet::::acc_active_redeem_order( + default_investment_id::(), + ) + .amount, + 0 + ); }); } - #[test_runtimes([centrifuge])] - fn convert_unknown_multilocation() { - let mut env = FudgeEnv::::default(); - - let unknown_location: Location = Location::new( - 1, - [ - Parachain(T::FudgeHandle::PARA_ID), - general_key([42].as_ref()), - ], + #[test_runtimes([development])] + fn fully_collect_redeem_order() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), ); - env.parachain_state_mut(|| { - assert!(>::convert(unknown_location).is_none()); - }); - } - - #[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 - ) - }); - } - } + let pool_id = POOL_ID; + let amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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(); - mod restricted_transfers { - use cfg_types::tokens::{CurrencyId::Native, FilterCurrency}; + // Create new pool + create_currency_pool::(pool_id, currency_id, currency_decimals.into()); - use super::*; - use crate::generic::envs::runtime_env::RuntimeEnv; + // 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(); - const TRANSFER_AMOUNT: u128 = 10; + // 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 + )); - fn xcm_location() -> VersionedLocation { - VersionedLocation::V4(Location::new( - 1, - AccountId32 { - id: Keyring::Alice.into(), - network: None, - }, - )) - } + // 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(), + } + )); - fn allowed_xcm_location() -> RestrictedTransferLocation { - RestrictedTransferLocation::Xcm(Box::new(xcm_location())) - } + // Enable liquidity pool transferability + enable_liquidity_pool_transferability::(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 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), + }; - #[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(), - ); + // Execute byte message + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); - 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); + // Remove events before collect execution + let events_since_collect: Vec<_> = frame_system::Pallet::::events() + .into_iter() + .filter(|e| !events_before_collect.contains(e)) + .collect(); - assert_ok!( - pallet_transfer_allowlist::Pallet::::add_transfer_allowance( - RawOrigin::Signed(Keyring::Alice.into()).into(), - FilterCurrency::All, - RestrictedTransferLocation::Local(Keyring::Bob.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())); - ( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Bob.id()), - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()), + // 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() + })); - let call = pallet_balances::Call::::transfer_allow_death { - dest: Keyring::Charlie.into(), - value: cfg(TRANSFER_AMOUNT), - }; - env.submit_now(Keyring::Alice, call).unwrap(); + // 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() + })); - 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(); + // 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() + })); - // 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()); + let sender = ::Sender::get(); - 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); + // 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([centrifuge])] - fn restrict_all() { - let mut env = RuntimeEnv::::from_parachain_storage( + #[test_runtimes([development])] + fn partially_collect_redemption_for_through_investments() { + let mut env = FudgeEnv::::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), - )], - }) + .add(genesis::balances::(cfg(1_000))) .storage(), ); - // 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()) - ) - ); - }); + setup_test(&mut env); - // Restrict USDC local 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()); - - 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 + let pool_id = POOL_ID; + let redeem_amount = 10 * decimals(12); + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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); - 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); + // 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_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()); + // 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!( - after_transfer_alice, - pre_transfer_alice - usdc(TRANSFER_AMOUNT) + // Pre collect assertions + assert!( + pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) ); - assert_eq!(after_transfer_bob, pre_transfer_bob + usdc(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - // Restrict also CFG local - env.parachain_state_mut(|| { - // NOTE: The para-id is not relevant here - register_cfg::(2031); + // 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 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()); + let sender = ::Sender::get(); - 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 + 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())); - let after_transfer_alice = - pallet_balances::Pallet::::free_balance(&Keyring::Alice.id()); - let after_transfer_charlie = - pallet_balances::Pallet::::free_balance(&Keyring::Charlie.id()); - - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + // 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 + ); - assert_ok!(pallet_restricted_tokens::Pallet::::transfer( + // Collect remainder through Investments + assert_ok!(pallet_investments::Pallet::::collect_redemptions_for( 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()); - - assert_eq!( - after_transfer_alice, - pre_transfer_alice - cfg(TRANSFER_AMOUNT) + investor.clone(), + default_investment_id::() + )); + assert!( + !pallet_investments::Pallet::::redemption_requires_collect( + &investor, + default_investment_id::() + ) ); - assert_eq!(after_transfer_bob, pre_transfer_bob + cfg(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + 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 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(), - ); + mod should_fail { + use super::*; - 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()), - ); + mod decrease_should_underflow { + use super::*; - 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 - ); + #[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 = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + 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, + }; - 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()); + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + ), + pallet_foreign_investments::Error::::TooMuchDecrease + ); + }); + } - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + #[test_runtimes([development])] + fn redeem_decrease_underflow() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - 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()); + setup_test(&mut env); - 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); - }); - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let redeem_amount: u128 = 10 * decimals(12); + let decrease_amount = redeem_amount + 1; + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + 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, + ); - #[test_runtimes([centrifuge])] - fn restrict_lp_eth_usdc_lp_transfer() { - 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), - )], - }) - .storage(), - ); + // 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, + }; - setup_xcm(&mut env); + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + ), + DispatchError::Arithmetic(sp_runtime::ArithmeticError::Underflow) + ); + }); + } + } - 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, - )); + mod should_throw_requires_collect { + use super::*; - 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(), - }); + #[test_runtimes([development])] + fn invest_requires_collect() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_ok!( - pallet_liquidity_pools_gateway::Pallet::::set_domain_router( - ::RuntimeOrigin::root(), - Domain::EVM(1), - router, - ) - ); + setup_test(&mut env); - let receiver = H160::from_slice( - &>::as_ref(&Keyring::Charlie.id()) - [0..20], - ); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount: u128 = 10 * decimals(12); + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + 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); - let domain_address = DomainAddress::EVM(1, receiver.into()); + // 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(), + } + )); - add_allowance::( - Keyring::Alice, - LP_ETH_USDC, - RestrictedTransferLocation::Address(domain_address.clone()), - ); + // 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 + ); - 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 - ); + // 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 + ); + }); + } - let total_issuance_pre = orml_tokens::Pallet::::total_issuance(LP_ETH_USDC); + #[test_runtimes([development])] + fn redeem_requires_collect() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_ok!(pallet_liquidity_pools::Pallet::::transfer( - RawOrigin::Signed(Keyring::Alice.into()).into(), - LP_ETH_USDC, - domain_address, - lp_eth_usdc(TRANSFER_AMOUNT), - )); + setup_test(&mut env); - assert_eq!( - orml_tokens::Pallet::::total_issuance(LP_ETH_USDC), - total_issuance_pre - lp_eth_usdc(TRANSFER_AMOUNT), - ); - }); - } + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let amount: u128 = 10 * decimals(12); + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + 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); - #[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(), - ); + // Mint more into DomainLocator required for subsequent invest attempt + assert_ok!(orml_tokens::Pallet::::mint_into( + default_investment_id::().into(), + &DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(), + 1, + )); - 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()), - ); + // 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(), + } + )); - 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 - ); + // 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 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()); + // 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_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + mod payment_payout_currency { + use super::*; - 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()); + #[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 - usdc(TRANSFER_AMOUNT) - ); - assert_eq!(after_transfer_bob, pre_transfer_bob + usdc(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); - }); - } + setup_test(&mut env); - #[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(), - Genesis::default() - .add(genesis::balances::(cfg(10))) - .storage(), - Default::default(), - ); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); - // 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() - ); - }); + create_currency_pool::(pool_id, pool_currency, currency_decimals.into()); + do_initial_increase_investment::( + pool_id, + amount, + investor.clone(), + pool_currency, + ); - setup_xcm(&mut env); + enable_usdt_trading::(pool_currency, amount, true, true, true); - setup_usdc_xcm(&mut env); + // 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.sibling_state_mut(|| { - register_usdc::(); - }); + #[test_runtimes([development])] + fn invalid_redeem_payout_currency() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - env.parachain_state_mut(|| { - register_usdc::(); + setup_test(&mut env); - let alice_initial_usdc = usdc(3_000); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); - assert_ok!(orml_tokens::Pallet::::mint_into( - USDC, - &Keyring::Alice.into(), - alice_initial_usdc - )); + 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(), + &DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(), + 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 + ); + }); + } - 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, - } - ] - ) - ))) - ) - ); + #[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_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 - ); + setup_test(&mut env); - 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, - } - ] - ) - .into() - ), - WeightLimit::Unlimited, - )); + env.parachain_state_mut(|| { + let pool_id = POOL_ID; + let investor = AccountConverter::domain_account_to_account( + DOMAIN_MOONBEAM, + Keyring::Bob.id(), + ); + let pool_currency = AUSD_CURRENCY_ID; + let currency_decimals = currency_decimals::AUSD; + let foreign_currency: CurrencyId = USDT_CURRENCY_ID; + let amount = 6 * decimals(18); - assert_eq!( - orml_tokens::Pallet::::free_balance(USDC, &Keyring::Alice.into()), - alice_initial_usdc - usdc(1_000), - ); - }); + 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(), + &DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(), + 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 + ); - // 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. + 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_dot_transfer() { - let mut env = RuntimeEnv::::from_parachain_storage( + #[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(), - DOT_ASSET_ID, - T::ExistentialDeposit::get() + dot(TRANSFER_AMOUNT), - )], - }) + .add(genesis::balances::(cfg(1_000))) .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 = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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 = + DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain().into_account(); + 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, ); - 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 + // 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, ); - 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()); - - assert_eq!(after_transfer_alice, pre_transfer_alice); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + // 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_restricted_tokens::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(), - 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()); - + investor.clone(), + default_investment_id::() + )); + assert!(orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &investor + ) + .is_zero()); assert_eq!( - after_transfer_alice, - pre_transfer_alice - dot(TRANSFER_AMOUNT) + orml_tokens::Pallet::::balance( + default_investment_id::().into(), + &sending_domain_locator + ), + invest_amount_pool_denominated * 2 ); - assert_eq!(after_transfer_bob, pre_transfer_bob + dot(TRANSFER_AMOUNT)); - assert_eq!(after_transfer_charlie, pre_transfer_charlie); + + 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() + })); }); } - #[test_runtimes([centrifuge])] - fn restrict_dot_xcm_transfer() { + /// 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(), ); - 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 = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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, ); - 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, + // 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_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - alice_initial_dot - dot(1), - ); - }); + // 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() + })); - env.pass(Blocks::ByNumber(2)); + // 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() + )); - env.relay_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() - ), - 79857365914 - ); + // 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() + })); }); } - } - mod transfers { - use super::*; + /// 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(), + ); - 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() - }, - }; + setup_test(&mut env); 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 + let pool_id = POOL_ID; + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(CurrencyId::Native), - )); - }); + // Increase such that active swap into USDT is initialized + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, + ); - env.sibling_state_mut(|| { - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - 0 + // 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() + })); - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta, - Some(cfg_in_sibling) + // 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() )); - }); - 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()), + 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() + })); - // 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(cfg_in_sibling, &Keyring::Bob.into()); - - // Verify that Keyring::Bob now has (amount transferred - fee) - assert_eq!(current_balance, transfer_amount - fee(18)); + let sender = ::Sender::get(); - // Sanity check for the actual amount Keyring::Bob ends up with - assert_eq!(current_balance, 4993570400000000000); + 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 test_cfg_transfers_to_and_from_sibling() { + #[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(), ); - 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 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); + setup_test(&mut env); env.parachain_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(&Keyring::Alice.into()), - alice_initial_balance + let pool_id = POOL_ID; + let investor = + AccountConverter::domain_account_to_account(DOMAIN_MOONBEAM, Keyring::Bob.id()); + 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, ); - }); - env.sibling_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::::free_balance(¶chain_account( - T::FudgeHandle::PARA_ID - )), - 0 + // Do first investment and fulfill swap order + do_initial_increase_investment::( + pool_id, + invest_amount_foreign_denominated, + investor.clone(), + foreign_currency, ); - assert_eq!( - orml_tokens::Pallet::::free_balance(cfg_in_sibling, &Keyring::Bob.into()), - bob_initial_balance + fulfill_swap_into_pool::( + pool_id, + default_order_id::(&investor), + invest_amount_pool_denominated, + invest_amount_foreign_denominated, + trader.clone(), ); - }); - 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()), + // 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 )); - // 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 - ); - }); + // 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(3)); + // 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.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!(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() + })); }); } + } +} - #[test_runtimes([centrifuge])] - fn transfer_ausd_to_centrifuge() { - let mut env = FudgeEnv::::default(); +mod routers { + use super::*; - setup_xcm(&mut env); + mod axelar_evm { + use std::ops::AddAssign; - let alice_initial_balance = ausd(10); - let transfer_amount = ausd(7); + use super::*; - env.sibling_state_mut(|| { - register_ausd::(); + #[test_runtimes([development])] + fn test_via_outbound_queue() { + let mut env = FudgeEnv::::from_parachain_storage( + Genesis::default() + .add(genesis::balances::(cfg(1_000))) + .storage(), + ); - assert_ok!(orml_tokens::Pallet::::deposit( - AUSD_CURRENCY_ID, - &Keyring::Alice.into(), - alice_initial_balance - )); + let test_domain = Domain::EVM(1); - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - ¶chain_account(T::FudgeHandle::PARA_ID) - ), - 0 - ); - }); + 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(|| { - register_ausd::(); - - assert_eq!( - orml_tokens::Pallet::::free_balance(AUSD_CURRENCY_ID, &Keyring::Bob.into()), - 0, - ); + pallet_evm::AccountCodes::::insert(axelar_contract_address, axelar_contract_code) }); - 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( - 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()), - )); + let transaction_call_cost = + env.parachain_state(|| ::config().gas_transaction_call); - assert_eq!( - orml_tokens::Pallet::::free_balance( - AUSD_CURRENCY_ID, - &Keyring::Alice.into() - ), - alice_initial_balance - transfer_amount - ); + 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), + }, + }; - // 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 - ); - }); + 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::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() + assert_ok!( + pallet_liquidity_pools_gateway::Pallet::::set_domain_router( + ::RuntimeOrigin::root(), + test_domain.clone(), + test_router, + ) ); }); - } - #[test_runtimes([centrifuge])] - fn transfer_dot_to_and_from_relay_chain() { - let mut env = FudgeEnv::::default(); + let sender = Keyring::Alice.id(); + let gateway_sender = env + .parachain_state(|| ::Sender::get()); - transfer_dot_from_relay_chain(&mut env); + let gateway_sender_h160: H160 = H160::from_slice( + &>::as_ref(&gateway_sender)[0..20], + ); - env.parachain_state_mut(|| { - let alice_initial_dot = - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()); + let msg = LiquidityPoolMessage::Transfer { + currency: 0, + sender: Keyring::Alice.id().into(), + receiver: Keyring::Bob.id().into(), + amount: 1_000u128, + }; - assert_ok!(pallet_xcm::Pallet::::force_xcm_version( - ::RuntimeOrigin::root(), - Box::new(Location::new(1, Junctions::Here)), - XCM_VERSION, - )); + // 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_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, - )); + let mut nonce = T::OutboundMessageNonce::one(); - assert_eq!( - orml_tokens::Pallet::::free_balance(DOT_ASSET_ID, &Keyring::Alice.into()), - alice_initial_dot - dot(1), - ); + 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.pass(Blocks::ByNumber(2)); + env.check_event(expected_event) + .expect("expected RouterExecutionFailure event"); - env.relay_state_mut(|| { - assert_eq!( - pallet_balances::Pallet::>::free_balance( - &Keyring::Alice.into() - ), - 79857365914 - ); - }); - } + nonce.add_assign(T::OutboundMessageNonce::one()); - #[test_runtimes([centrifuge])] - fn transfer_foreign_sibling_to_centrifuge() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(10))) - .storage(), - ); + 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), + ); - setup_xcm(&mut env); + as OutboundQueue>::submit( + sender.clone(), + test_domain.clone(), + msg.clone(), + ) + })); - 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); + let expected_event = + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { + sender: gateway_sender.clone(), + domain: test_domain.clone(), + message: msg.clone(), + nonce, + }; - 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), - )); + env.pass(Blocks::UntilEvent { + event: expected_event.clone().into(), + limit: 3, }); - 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.check_event(expected_event) + .expect("expected OutboundMessageExecutionSuccess event"); - env.sibling_state_mut(|| { - assert_ok!(pallet_balances::Pallet::::force_set_balance( - ::RuntimeOrigin::root(), - Keyring::Alice.id().into(), - transfer_amount * 2, - )); + // Router not found + let unused_domain = Domain::EVM(1234); - 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() + env.parachain_state_mut(|| { + assert_noop!( + as OutboundQueue>::submit( + sender, + unused_domain.clone(), + msg, ), - 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_gateway::Error::::RouterNotFound ); }); + } + } - env.pass(Blocks::ByNumber(3)); + mod ethereum_xcm { + use super::*; - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(sibling_asset_id, &Keyring::Bob.into()); + mod utils { + use super::*; - // 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() - ) + 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(), ); - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 993264000000000000); - }); - } - #[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(), - ); + setup_test(&mut env); - setup_xcm(&mut env); + enable_para_to_sibling_communication::(&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 - )); - 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) - ); - }); + let msg = Message::::Transfer { + currency: 0, + sender: Keyring::Alice.into(), + receiver: Keyring::Bob.into(), + amount: 1_000u128, + }; - env.parachain_state_mut(|| { - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - meta.clone(), - Some(usdc_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!(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(), - } - ] + assert_ok!( + pallet_liquidity_pools_gateway::Pallet::::set_domain_router( + ::RuntimeOrigin::root(), + TEST_DOMAIN, + domain_router, ) - .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 - ); - }); + ); - env.pass(Blocks::ByNumber(3)); + assert_ok!( + as OutboundQueue>::submit( + Keyring::Alice.into(), + TEST_DOMAIN, + msg.clone(), + ) + ); + }); - env.parachain_state_mut(|| { - let bob_balance = - orml_tokens::Pallet::::free_balance(usdc_asset_id, &Keyring::Bob.into()); + let gateway_sender = env.parachain_state(|| { + ::Sender::get() + }); - // Sanity check to ensure the calculated is what is expected - assert_eq!(bob_balance, 11993571); - }); - } - } -} + let expected_event = + pallet_liquidity_pools_gateway::Event::::OutboundMessageExecutionSuccess { + sender: gateway_sender, + domain: TEST_DOMAIN, + message: msg, + nonce: T::OutboundMessageNonce::one(), + }; -mod all { - use super::*; + env.pass(Blocks::UntilEvent { + event: expected_event.clone().into(), + limit: 3, + }); - mod restricted_calls { - use super::*; + env.check_event(expected_event) + .expect("expected OutboundMessageExecutionSuccess event"); + } - #[test_runtimes(all)] - fn xtokens_transfer() { - let mut env = FudgeEnv::::default(); + 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(), + }; - 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 - ); - }); - } + DomainRouter::AxelarXCM(router) + }, + ) + } - #[test_runtimes(all)] - fn xtokens_transfer_multiasset() { - let mut env = FudgeEnv::::default(); + 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(), + }; - 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), + DomainRouter::EthereumXCM(router) }, - ], - ); - let tranche_asset = VersionedAsset::from(Asset::from(( - AssetId(tranche_location), - Fungibility::Fungible(42), - ))); - - 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 - ); - }); + ) + } } - #[test_runtimes(all)] - fn xtokens_transfer_multiassets() { - let mut env = FudgeEnv::::default(); + use utils::*; - 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 = Asset::from((AssetId(tranche_location), Fungibility::Fungible(42))); + const TEST_DOMAIN: Domain = Domain::EVM(1); - 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 - ); - }); + #[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::()); } } } 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..0aff7d8415 100644 --- a/runtime/integration-tests/src/generic/cases/lp/transfers.rs +++ b/runtime/integration-tests/src/generic/cases/lp/transfers.rs @@ -10,15 +10,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use cfg_primitives::{Balance, PoolId}; -use cfg_traits::Seconds; +use cfg_primitives::Balance; use cfg_types::{ domain_address::{Domain, DomainAddress}, - permissions::PoolRole, tokens::CurrencyId, }; use ethabi::{ethereum_types::U256, Token}; -use frame_support::{assert_noop, traits::OriginTrait}; +use frame_support::traits::OriginTrait; use frame_system::pallet_prelude::OriginFor; use pallet_liquidity_pools::Message; use sp_core::ByteArray; @@ -143,13 +141,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::); }); @@ -344,48 +342,3 @@ fn transfer_tranche_tokens_domain_to_local() { ); }); } - -#[test_runtimes(all)] -fn transferring_invalid_tranche_tokens_should_fail() { - const INVALID_POOL_ID: PoolId = 100; - const INVALID_TRANCHE_ID: [u8; 16] = [0; 16]; - let mut env = super::setup_full::(); - - env.state_mut(|_| { - crate::generic::utils::pool::give_role::( - lp::utils::remote_account_of::(Keyring::TrancheInvestor(1)), - POOL_A, - PoolRole::TrancheInvestor(INVALID_TRANCHE_ID, Seconds::MAX), - ); - crate::generic::utils::pool::give_role::( - lp::utils::remote_account_of::(Keyring::TrancheInvestor(1)), - INVALID_POOL_ID, - PoolRole::TrancheInvestor(INVALID_TRANCHE_ID, Seconds::MAX), - ); - }); - - let destination = DomainAddress::EVM(EVM_DOMAIN_CHAIN_ID, Keyring::TrancheInvestor(1).into()); - env.state(|_evm| { - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( - OriginFor::::signed(Keyring::TrancheInvestor(1).into()), - INVALID_POOL_ID, - INVALID_TRANCHE_ID, - destination.clone(), - AMOUNT - ), - pallet_liquidity_pools::Error::::PoolNotFound - ); - - assert_noop!( - pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( - OriginFor::::signed(Keyring::TrancheInvestor(1).into()), - POOL_A, - INVALID_TRANCHE_ID, - destination, - AMOUNT - ), - pallet_liquidity_pools::Error::::TrancheNotFound - ); - }); -} 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..30f3e2d6e7 100644 --- a/runtime/integration-tests/src/generic/cases/restricted_transfers.rs +++ b/runtime/integration-tests/src/generic/cases/restricted_transfers.rs @@ -10,34 +10,43 @@ // 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::{ + default_metadata, 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, 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 +75,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 +157,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 +173,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 +181,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 +195,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 +208,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 +221,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 +241,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 +271,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 +287,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..40e0a3d2da 100644 --- a/runtime/integration-tests/src/generic/utils/currency.rs +++ b/runtime/integration-tests/src/generic/utils/currency.rs @@ -5,11 +5,16 @@ 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 +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 +36,7 @@ pub trait CurrencyInfo { &self.symbol() } - fn location(&self) -> Option { + fn location(&self) -> Option { None } @@ -89,7 +94,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 +120,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 +146,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/democracy.rs b/runtime/integration-tests/src/generic/utils/democracy.rs index 8fa8a9f063..e69de29bb2 100644 --- a/runtime/integration-tests/src/generic/utils/democracy.rs +++ b/runtime/integration-tests/src/generic/utils/democracy.rs @@ -1,277 +0,0 @@ -use std::ops::Add; - -use cfg_primitives::{Balance, BlockNumber}; -use frame_support::{dispatch::GetDispatchInfo, traits::Bounded, weights::Weight}; -use pallet_collective::{Call as CouncilCall, MemberCount, ProposalIndex}; -use pallet_democracy::{ - AccountVote, Call as DemocracyCall, Conviction, PropIndex, ReferendumIndex, ReferendumInfo, - Vote, -}; -use pallet_preimage::Call as PreimageCall; -use parity_scale_codec::Encode; -use runtime_common::instances::CouncilCollective; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash}; - -use crate::{ - generic::{ - config::Runtime, - env::{Blocks, Env}, - envs::fudge_env::FudgeSupport, - }, - utils::accounts::Keyring, -}; - -pub fn note_preimage(call: T::RuntimeCallExt) -> T::RuntimeCallExt { - let encoded_call = call.encode(); - - PreimageCall::note_preimage { - bytes: encoded_call, - } - .into() -} - -pub fn external_propose_majority(call: T::RuntimeCallExt) -> T::RuntimeCallExt { - let hash = BlakeTwo256::hash_of(&call); - - DemocracyCall::external_propose_majority { - proposal: Bounded::Legacy { - hash, - dummy: Default::default(), - }, - } - .into() -} - -pub fn fast_track( - proposal_hash: H256, - voting_period: BlockNumber, - delay: BlockNumber, -) -> T::RuntimeCallExt { - DemocracyCall::fast_track { - proposal_hash, - voting_period, - delay, - } - .into() -} - -pub fn execute_via_democracy( - env: &mut impl Env, - council_members: Vec, - original_call: T::RuntimeCallExt, - council_threshold: MemberCount, - voting_period: BlockNumber, - starting_prop_index: PropIndex, - starting_ref_index: ReferendumIndex, -) -> (PropIndex, ReferendumIndex) { - let original_call_hash = BlakeTwo256::hash_of(&original_call); - - env.submit_later( - council_members[0].into(), - note_preimage::(original_call.clone()), - ) - .expect("Preimage noting is successful"); - - env.pass(Blocks::UntilEvent { - event: pallet_preimage::Event::::Noted { - hash: original_call_hash, - } - .into(), - limit: 3, - }); - - let external_propose_majority_call = external_propose_majority::(original_call); - - execute_collective_proposal::( - env, - &council_members, - external_propose_majority_call, - council_threshold, - starting_prop_index, - ); - - let fast_track_call = fast_track::(original_call_hash, voting_period, 0); - - execute_collective_proposal::( - env, - &council_members, - fast_track_call, - council_threshold, - starting_prop_index + 1, - ); - - let vote = AccountVote::::Standard { - vote: Vote { - aye: true, - conviction: Conviction::Locked2x, - }, - balance: 1_000_000u128, - }; - - execute_democracy_vote(env, &council_members, starting_ref_index, vote); - - (starting_prop_index + 2, starting_ref_index + 1) -} - -pub fn democracy_vote( - ref_index: ReferendumIndex, - vote: AccountVote, -) -> T::RuntimeCallExt { - DemocracyCall::vote { ref_index, vote }.into() -} - -fn execute_democracy_vote( - env: &mut impl Env, - voters: &Vec, - referendum_index: ReferendumIndex, - acc_vote: AccountVote, -) { - for acc in voters { - let ref_info = env.parachain_state(|| { - pallet_democracy::ReferendumInfoOf::::get(referendum_index).unwrap() - }); - - if let ReferendumInfo::Finished { .. } = ref_info { - // Referendum might be finished by the time all voters get to vote. - break; - } - - env.submit_later(*acc, democracy_vote::(referendum_index, acc_vote)) - .expect("Voting is successful"); - - env.pass(Blocks::UntilEvent { - event: pallet_democracy::Event::::Voted { - voter: acc.id(), - ref_index: referendum_index, - vote: acc_vote, - } - .into(), - limit: 3, - }); - } -} - -pub fn collective_propose( - proposal: T::RuntimeCallExt, - threshold: MemberCount, -) -> T::RuntimeCallExt { - let proposal_len = proposal.encode().len(); - - CouncilCall::propose { - threshold, - proposal: Box::new(proposal), - length_bound: proposal_len as u32, - } - .into() -} - -pub fn collective_vote( - proposal: H256, - index: ProposalIndex, - approve: bool, -) -> T::RuntimeCallExt { - CouncilCall::vote { - proposal, - index, - approve, - } - .into() -} - -pub fn collective_close( - proposal_hash: H256, - index: ProposalIndex, - proposal_weight_bound: Weight, - length_bound: u32, -) -> T::RuntimeCallExt { - CouncilCall::close { - proposal_hash, - index, - proposal_weight_bound, - length_bound, - } - .into() -} - -fn execute_collective_proposal( - env: &mut impl Env, - council_members: &Vec, - proposal: T::RuntimeCallExt, - council_threshold: MemberCount, - prop_index: PropIndex, -) { - let prop_hash = BlakeTwo256::hash_of(&proposal); - - env.submit_later( - council_members[0].into(), - collective_propose::(proposal.clone(), council_threshold), - ) - .expect("Collective proposal is successful"); - - env.pass(Blocks::UntilEvent { - event: pallet_collective::Event::::Proposed { - account: council_members[0].into(), - proposal_index: prop_index, - proposal_hash: prop_hash, - threshold: council_threshold, - } - .into(), - limit: 3, - }); - - for (index, acc) in council_members.iter().enumerate() { - env.submit_later(*acc, collective_vote::(prop_hash, prop_index, true)) - .expect("Collective voting is successful"); - - env.pass(Blocks::UntilEvent { - event: pallet_collective::Event::::Voted { - account: council_members[0].into(), - proposal_hash: prop_hash, - voted: true, - yes: (index + 1) as u32, - no: 0, - } - .into(), - limit: 3, - }); - } - - let proposal_weight = env.parachain_state(|| { - let external_proposal = - pallet_collective::ProposalOf::::get(prop_hash).unwrap(); - - external_proposal.get_dispatch_info().weight - }); - - env.submit_later( - council_members[0].into(), - collective_close::( - prop_hash, - prop_index, - proposal_weight.add(1.into()), - (proposal.encoded_size() + 1) as u32, - ), - ) - .expect("Collective close is successful"); - - env.pass(Blocks::UntilEvent { - event: pallet_collective::Event::::Closed { - proposal_hash: prop_hash, - yes: council_members.len() as u32, - no: 0, - } - .into(), - limit: 3, - }); - - env.check_event(pallet_collective::Event::::Approved { - proposal_hash: prop_hash, - }) - .expect("Approved event is present."); - env.check_event(pallet_collective::Event::::Executed { - proposal_hash: prop_hash, - result: Ok(()), - }) - .expect("Executed event is present."); -} diff --git a/runtime/integration-tests/src/generic/utils/genesis.rs b/runtime/integration-tests/src/generic/utils/genesis.rs index b3507332fb..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 { - pallet_collective::GenesisConfig:: { +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/mod.rs b/runtime/integration-tests/src/generic/utils/mod.rs index 992b4916ec..ddc868451d 100644 --- a/runtime/integration-tests/src/generic/utils/mod.rs +++ b/runtime/integration-tests/src/generic/utils/mod.rs @@ -9,7 +9,6 @@ //! Divide this utilities into files when it grows pub mod currency; -pub mod democracy; pub mod evm; pub mod genesis; pub mod pool; diff --git a/runtime/integration-tests/src/generic/utils/xcm.rs b/runtime/integration-tests/src/generic/utils/xcm.rs index 97c788071b..0380bf0ddf 100644 --- a/runtime/integration-tests/src/generic/utils/xcm.rs +++ b/runtime/integration-tests/src/generic/utils/xcm.rs @@ -1,69 +1,108 @@ -use frame_support::{assert_ok, traits::OriginTrait}; +use cfg_primitives::AccountId; +use cfg_types::tokens::{ + default_metadata, 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, + }, }; -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, - )); + // 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_open_hrmp_channel( - as frame_system::Config>::RuntimeOrigin::root(), - Id::from(T::FudgeHandle::SIBLING_ID), - Id::from(T::FudgeHandle::PARA_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/accounts.rs b/runtime/integration-tests/src/utils/accounts.rs index 46f28d84b8..4a111daa34 100644 --- a/runtime/integration-tests/src/utils/accounts.rs +++ b/runtime/integration-tests/src/utils/accounts.rs @@ -14,6 +14,7 @@ use ethabi::ethereum_types::{H160, H256}; use frame_support::traits::OriginTrait; +use runtime_common::account_conversion::AccountConverter; use sp_core::{ecdsa, ed25519, sr25519, Hasher, Pair as PairT}; use sp_runtime::AccountId32; @@ -44,7 +45,7 @@ impl Keyring { /// NOTE: Needs to be executed in an externalities environment pub fn id_ecdsa(self) -> AccountId32 { - runtime_common::account_conversion::AccountConverter::into_account_id::(self.into()) + AccountConverter::evm_address_to_account::(self.into()) } pub fn as_multi(self) -> sp_runtime::MultiSigner { 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)); + } +} diff --git a/scripts/js/upgrade/README.md b/scripts/js/runtime-upgrade-local/README.md similarity index 100% rename from scripts/js/upgrade/README.md rename to scripts/js/runtime-upgrade-local/README.md diff --git a/scripts/js/upgrade/index.js b/scripts/js/runtime-upgrade-local/index.js similarity index 100% rename from scripts/js/upgrade/index.js rename to scripts/js/runtime-upgrade-local/index.js diff --git a/scripts/js/upgrade/package.json b/scripts/js/runtime-upgrade-local/package.json similarity index 100% rename from scripts/js/upgrade/package.json rename to scripts/js/runtime-upgrade-local/package.json diff --git a/scripts/js/upgrade/yarn.lock b/scripts/js/runtime-upgrade-local/yarn.lock similarity index 100% rename from scripts/js/upgrade/yarn.lock rename to scripts/js/runtime-upgrade-local/yarn.lock diff --git a/scripts/js/runtime-upgrade-remote/configs/demo.json b/scripts/js/runtime-upgrade-remote/configs/demo.json new file mode 100644 index 0000000000..8a82088e39 --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/configs/demo.json @@ -0,0 +1,7 @@ +{ + "endpoint": "wss://fullnode.demo.k-f.dev", + "wasmFile": "./development.wasm", + "privateKey": "", + "sudo": true, + "councilMembers": [] + } \ No newline at end of file diff --git a/scripts/js/runtime-upgrade-remote/configs/development.json b/scripts/js/runtime-upgrade-remote/configs/development.json new file mode 100644 index 0000000000..d018cedb4a --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/configs/development.json @@ -0,0 +1,7 @@ +{ + "endpoint": "wss://fullnode.development.cntrfg.com", + "wasmFile": "./development.wasm", + "privateKey": "//Alice", + "sudo": true, + "councilMembers": [] +} \ No newline at end of file diff --git a/scripts/js/runtime-upgrade-remote/index.js b/scripts/js/runtime-upgrade-remote/index.js new file mode 100644 index 0000000000..a784aa96ba --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/index.js @@ -0,0 +1,148 @@ +const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api'); +const { u8aToHex } = require('@polkadot/util'); +const { blake2AsHex, blake2AsU8a } = require('@polkadot/util-crypto'); +const fs = require('fs'); +const path = require('path'); + +// Load configuration +const configPath = path.resolve(__dirname, 'config.json'); +const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + +const run = async () => { + let exitCode = 0; + try { + // Validate configuration + if (!config.endpoint || !config.wasmFile || !config.privateKey) { + console.error("Missing configuration parameters. Please ensure 'endpoint', 'wasmFile', and 'privateKey' are specified in the corresponding configs/*.json."); + process.exit(1); + } + + console.log("Configuration loaded:", config); + + const wsProvider = new WsProvider(config.endpoint); + const api = await ApiPromise.create({ provider: wsProvider }); + + console.log("Connected to the parachain at:", config.endpoint); + + const keyring = new Keyring({ type: "sr25519" }); + let user; + if (config.privateKey.startsWith('//')) { + user = keyring.addFromUri(config.privateKey); + } else { + user = keyring.addFromSeed(config.privateKey); + } + + console.log(`Using account: ${user.address}`); + + const wasm = fs.readFileSync(config.wasmFile); + const wasmHash = blake2AsHex(wasm); + const wasmBytes = u8aToHex(wasm); + + console.log("WASM file loaded and ready for deployment"); + + if (config.sudo) { + console.log("Using sudo to perform the runtime upgrade"); + await sudoAuthorize(api, user, wasmHash); + await enactUpgrade(api, user, wasmBytes); + } else { + console.error("Unsupported"); + } + // Check for specific events or transaction success as needed + } catch (error) { + console.error('Error:', error); + exitCode = 1; + } finally { + process.exit(exitCode); + } +}; + +async function sudoAuthorize(api, sudoAccount, wasmHex) { + const nonce = await api.rpc.system.accountNextIndex(sudoAccount.address) + + return new Promise(async (resolve, reject) => { + try { + // Authorize the upgrade + const authorizeTx = api.tx.sudo.sudo( + api.tx.parachainSystem.authorizeUpgrade(wasmHex, true) + ); + + const unsub = await authorizeTx.signAndSend(sudoAccount, { nonce }, ({ status, dispatchError, events }) => { + console.log(`Authorizing upgrade with status ${status}`); + if (status.isInBlock) { + console.log(`Authorization included in block ${status.asInBlock}`); + resolve(); + unsub(); + } + checkError(api, reject, dispatchError, events) + }); + } + catch (error) { + reject(error) + } + }); +} + +async function enactUpgrade(api, sudoAccount, wasmFile) { + const nonce = await api.rpc.system.accountNextIndex(sudoAccount.address) + + return new Promise(async (resolve, reject) => { + try { + // Enact the authorized upgrade + const enactTx = api.tx.parachainSystem.enactAuthorizedUpgrade(wasmFile); + + const unsub = await enactTx.signAndSend(sudoAccount, { nonce }, ({ status, dispatchError, events }) => { + console.log(`Enacting upgrade with status ${status}`); + if (status.isInBlock) { + console.log(`Enactment included in block ${status}`); + resolve(); + unsub(); + } + checkError(api, reject, dispatchError, events) + }); + } + catch (error) { + reject(error) + } + }); +} + +function checkError(api, reject, dispatchError, events) { + if (dispatchError) { + if (dispatchError.isModule) { + // for module errors, we have the section indexed, lookup + const decoded = api.registry.findMetaError(dispatchError.asModule); + const { docs, name, section } = decoded; + + console.error(`${section}.${name}: ${docs.join(' ')}`); + } else { + // Other, CannotLookup, BadOrigin, no extra info + console.error(dispatchError.toString()); + } + reject(dispatchError) + } else if (events) { + events + // find/filter for failed events + .filter(({ event }) => + api.events.system.ExtrinsicFailed.is(event) + ) + // we know that data for system.ExtrinsicFailed is + // (DispatchError, DispatchInfo) + .forEach(({ event: { data: [error, info] } }) => { + if (error.isModule) { + // for module errors, we have the section indexed, lookup + const decoded = api.registry.findMetaError(error.asModule); + const { docs, method, section } = decoded; + const error = `${section}.${method}: ${docs.join(' ')}` + + console.error(error); + reject(error) + } else { + // Other, CannotLookup, BadOrigin, no extra info + console.error(error.toString()); + reject(error.toString()) + } + }); + } +} + +run(); \ No newline at end of file diff --git a/scripts/js/runtime-upgrade-remote/package.json b/scripts/js/runtime-upgrade-remote/package.json new file mode 100644 index 0000000000..c16a0daa5f --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/package.json @@ -0,0 +1,16 @@ +{ + "name": "parachain-upgrade", + "version": "1.0.0", + "description": "Script to handle parachain upgrades", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@polkadot/api": "^11.3.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1" + }, + "author": "", + "license": "ISC" +} diff --git a/scripts/js/runtime-upgrade-remote/perform-upgrade.sh b/scripts/js/runtime-upgrade-remote/perform-upgrade.sh new file mode 100644 index 0000000000..34f3422842 --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/perform-upgrade.sh @@ -0,0 +1,40 @@ +#!/bin/bash +echo "Please select the environment (development, demo):" +read -r ENVIRONMENT + +# Check if the privateKey is empty for demo environment +if [ "$ENVIRONMENT" == "demo" ]; then + PRIVATE_KEY=$(jq -r '.privateKey' ./configs/demo.json) + if [ -z "$PRIVATE_KEY" ]; then + echo "Error: privateKey is empty in ./configs/demo.json. Please retrieve it from 1Password." + exit 1 + fi +fi + +# # Install NVM and node if not present in your mac: +# brew install nvm && echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc && echo '[ -s "$NVM_DIR/nvm.sh" ] \ +# && \. "$NVM_DIR/nvm.sh"' >> ~/.zshrc && source ~/.zshrc && nvm install node + +# Define the tag and calculate the short git hash +TAG="v0.11.1-rc1" +GIT_HASH=$(git rev-parse --short=7 $TAG) + +# Download the WASM file from Google Cloud Storage +echo "Downloading WASM file..." +if [ "$ENVIRONMENT" == "demo" ]; then + gsutil cp gs://centrifuge-wasm-repo/development/development-"$GIT_HASH".wasm ./"${ENVIRONMENT}".wasm +else + gsutil cp gs://centrifuge-wasm-repo/"${ENVIRONMENT}"/"${ENVIRONMENT}"-"$GIT_HASH".wasm ./"${ENVIRONMENT}".wasm +fi + +# Copy the corresponding configuration file +echo "Copying configuration file..." +cp ./configs/"${ENVIRONMENT}".json ./config.json + +# Run the node script +echo "Running node index.js..." +node index.js +echo "Cleaning up..." +rm ./config.json +rm ./"${ENVIRONMENT}".wasm + diff --git a/scripts/js/runtime-upgrade-remote/yarn.lock b/scripts/js/runtime-upgrade-remote/yarn.lock new file mode 100644 index 0000000000..eabdc9b7d5 --- /dev/null +++ b/scripts/js/runtime-upgrade-remote/yarn.lock @@ -0,0 +1,564 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@noble/curves@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@polkadot-api/json-rpc-provider-proxy@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1.tgz#bb5c943642cdf0ec7bc48c0a2647558b9fcd7bdb" + integrity sha512-gmVDUP8LpCH0BXewbzqXF2sdHddq1H1q+XrAW2of+KZj4woQkIGBRGTJHeBEVHe30EB+UejR1N2dT4PO/RvDdg== + +"@polkadot-api/json-rpc-provider@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz#333645d40ccd9bccfd1f32503f17e4e63e76e297" + integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== + +"@polkadot-api/metadata-builders@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1.tgz#a76b48febef9ea72be8273d889e2677101045a05" + integrity sha512-GCI78BHDzXAF/L2pZD6Aod/yl82adqQ7ftNmKg51ixRL02JpWUA+SpUKTJE5MY1p8kiJJIo09P2um24SiJHxNA== + dependencies: + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/observable-client@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/observable-client/-/observable-client-0.1.0.tgz#472045ea06a2bc4bccdc2db5c063eadcbf6f5351" + integrity sha512-GBCGDRztKorTLna/unjl/9SWZcRmvV58o9jwU2Y038VuPXZcr01jcw/1O3x+yeAuwyGzbucI/mLTDa1QoEml3A== + dependencies: + "@polkadot-api/metadata-builders" "0.0.1" + "@polkadot-api/substrate-bindings" "0.0.1" + "@polkadot-api/substrate-client" "0.0.1" + "@polkadot-api/utils" "0.0.1" + +"@polkadot-api/substrate-bindings@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1.tgz#c4b7f4d6c3672d2c15cbc6c02964f014b73cbb0b" + integrity sha512-bAe7a5bOPnuFVmpv7y4BBMRpNTnMmE0jtTqRUw/+D8ZlEHNVEJQGr4wu3QQCl7k1GnSV1wfv3mzIbYjErEBocg== + dependencies: + "@noble/hashes" "^1.3.1" + "@polkadot-api/utils" "0.0.1" + "@scure/base" "^1.1.1" + scale-ts "^1.6.0" + +"@polkadot-api/substrate-client@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-client/-/substrate-client-0.0.1.tgz#0e8010a0abe2fb47d6fa7ab94e45e1d0de083314" + integrity sha512-9Bg9SGc3AwE+wXONQoW8GC00N3v6lCZLW74HQzqB6ROdcm5VAHM4CB/xRzWSUF9CXL78ugiwtHx3wBcpx4H4Wg== + +"@polkadot-api/utils@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/utils/-/utils-0.0.1.tgz#908b22becac705149d7cc946532143d0fb003bfc" + integrity sha512-3j+pRmlF9SgiYDabSdZsBSsN5XHbpXOAce1lWj56IEEaFZVjsiCaxDOA7C9nCcgfVXuvnbxqqEGQvnY+QfBAUw== + +"@polkadot/api-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-11.3.1.tgz#c7deeac438b017613e244c25505216a9d4c3977e" + integrity sha512-Yj+6rb6h0WwY3yJ+UGhjGW+tyMRFUMsKQuGw+eFsXdjiNU9UoXsAqA2dG7Q1F+oeX/g+y2gLGBezNoCwbl6HfA== + dependencies: + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/api-base@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-11.3.1.tgz#6c74c88d4a4b3d22344bb8715a135493f5a3dd33" + integrity sha512-b8UkNL00NN7+3QaLCwL5cKg+7YchHoKCAhwKusWHNBZkkO6Oo2BWilu0dZkPJOyqV9P389Kbd9+oH+SKs9u2VQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api-derive@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-11.3.1.tgz#3617655b6dab56d5beb8efbf7383ab457370df35" + integrity sha512-9dopzrh4cRuft1nANmBvMY/hEhFDu0VICMTOGxQLOl8NMfcOFPTLAN0JhSBUoicGZhV+c4vpv01NBx/7/IL1HA== + dependencies: + "@polkadot/api" "11.3.1" + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/api@11.3.1", "@polkadot/api@^11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-11.3.1.tgz#6092aea8147ea03873b3f383cceae0390a67f71d" + integrity sha512-q4kFIIHTLvKxM24b0Eo8hJevsPMme+aITJGrDML9BgdZYTRN14+cu5nXiCsQvaEamdyYj+uCXWe2OV9X7pPxsA== + dependencies: + "@polkadot/api-augment" "11.3.1" + "@polkadot/api-base" "11.3.1" + "@polkadot/api-derive" "11.3.1" + "@polkadot/keyring" "^12.6.2" + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-core" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/types-known" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + eventemitter3 "^5.0.1" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/keyring@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-12.6.2.tgz#6067e6294fee23728b008ac116e7e9db05cecb9b" + integrity sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw== + dependencies: + "@polkadot/util" "12.6.2" + "@polkadot/util-crypto" "12.6.2" + tslib "^2.6.2" + +"@polkadot/networks@12.6.2", "@polkadot/networks@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-12.6.2.tgz#791779fee1d86cc5b6cd371858eea9b7c3f8720d" + integrity sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w== + dependencies: + "@polkadot/util" "12.6.2" + "@substrate/ss58-registry" "^1.44.0" + tslib "^2.6.2" + +"@polkadot/rpc-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-11.3.1.tgz#b589ef5b7ab578cf274077604543071ce9889301" + integrity sha512-2PaDcKNju4QYQpxwVkWbRU3M0t340nMX9cMo+8awgvgL1LliV/fUDZueMKLuSS910JJMTPQ7y2pK4eQgMt08gQ== + dependencies: + "@polkadot/rpc-core" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/rpc-core@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-11.3.1.tgz#c23e23ee5c403c4edb207d603ae4dc16e69bc710" + integrity sha512-KKNepsDd/mpmXcA6v/h14eFFPEzLGd7nrvx2UUXUxoZ0Fq2MH1hplP3s93k1oduNY/vOXJR2K9S4dKManA6GVQ== + dependencies: + "@polkadot/rpc-augment" "11.3.1" + "@polkadot/rpc-provider" "11.3.1" + "@polkadot/types" "11.3.1" + "@polkadot/util" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/rpc-provider@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-11.3.1.tgz#1d1289bf42d065b5f04f9baa46ef90d96940819e" + integrity sha512-pqERChoHo45hd3WAgW8UuzarRF+G/o/eXEbl0PXLubiayw4X4qCmIzmtntUcKYgxGNcYGZaG87ZU8OjN97m6UA== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-support" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + "@polkadot/x-fetch" "^12.6.2" + "@polkadot/x-global" "^12.6.2" + "@polkadot/x-ws" "^12.6.2" + eventemitter3 "^5.0.1" + mock-socket "^9.3.1" + nock "^13.5.0" + tslib "^2.6.2" + optionalDependencies: + "@substrate/connect" "0.8.10" + +"@polkadot/types-augment@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-11.3.1.tgz#1f7f553f0ca6eb8fbc0306901edc045fe18729e1" + integrity sha512-eR3HVpvUmB3v7q2jTWVmVfAVfb1/kuNn7ij94Zqadg/fuUq0pKqIOKwkUj3OxRM3A/5BnW3MbgparjKD3r+fyw== + dependencies: + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-codec@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-11.3.1.tgz#2767cf482cd49afdd5dce9701615f68ec59cec5e" + integrity sha512-i7IiiuuL+Z/jFoKTA9xeh4wGQnhnNNjMT0+1ohvlOvnFsoKZKFQQOaDPPntGJVL1JDCV+KjkN2uQKZSeW8tguQ== + dependencies: + "@polkadot/util" "^12.6.2" + "@polkadot/x-bigint" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-create@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-11.3.1.tgz#3ac2c8283f61555f9e572ca30e3485b95a0a54e2" + integrity sha512-pBXtpz5FehcRJ6j5MzFUIUN8ZWM7z6HbqK1GxBmYbJVRElcGcOg7a/rL2pQVphU0Rx1E8bSO4thzGf4wUxSX7w== + dependencies: + "@polkadot/types-codec" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-known@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-11.3.1.tgz#fc34ed29ac2474db6b66805a15d12008226346bb" + integrity sha512-3BIof7u6tn9bk3ZCIxA07iNoQ3uj4+vn3DTOjCKECozkRlt6V+kWRvqh16Hc0SHMg/QjcMb2fIu/WZhka1McUQ== + dependencies: + "@polkadot/networks" "^12.6.2" + "@polkadot/types" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types-support@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-11.3.1.tgz#dee02a67784baa13177fe9957f5d8d62e8a7e570" + integrity sha512-jTFz1GKyF7nI29yIOq4v0NiWTOf5yX4HahJNeFD8TcxoLhF+6tH/XXqrUXJEfbaTlSrRWiW1LZYlb+snctqKHA== + dependencies: + "@polkadot/util" "^12.6.2" + tslib "^2.6.2" + +"@polkadot/types@11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-11.3.1.tgz#bab61b701218158099e3f548d20efc27cbf1287f" + integrity sha512-5c7uRFXQTT11Awi6T0yFIdAfD6xGDAOz06Kp7M5S9OGNZY28wSPk5x6BYfNphWPaIBmHHewYJB5qmnrdYQAWKQ== + dependencies: + "@polkadot/keyring" "^12.6.2" + "@polkadot/types-augment" "11.3.1" + "@polkadot/types-codec" "11.3.1" + "@polkadot/types-create" "11.3.1" + "@polkadot/util" "^12.6.2" + "@polkadot/util-crypto" "^12.6.2" + rxjs "^7.8.1" + tslib "^2.6.2" + +"@polkadot/util-crypto@12.6.2", "@polkadot/util-crypto@^12.5.1", "@polkadot/util-crypto@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz#d2d51010e8e8ca88951b7d864add797dad18bbfc" + integrity sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg== + dependencies: + "@noble/curves" "^1.3.0" + "@noble/hashes" "^1.3.3" + "@polkadot/networks" "12.6.2" + "@polkadot/util" "12.6.2" + "@polkadot/wasm-crypto" "^7.3.2" + "@polkadot/wasm-util" "^7.3.2" + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-randomvalues" "12.6.2" + "@scure/base" "^1.1.5" + tslib "^2.6.2" + +"@polkadot/util@12.6.2", "@polkadot/util@^12.5.1", "@polkadot/util@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-12.6.2.tgz#9396eff491221e1f0fd28feac55fc16ecd61a8dc" + integrity sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw== + dependencies: + "@polkadot/x-bigint" "12.6.2" + "@polkadot/x-global" "12.6.2" + "@polkadot/x-textdecoder" "12.6.2" + "@polkadot/x-textencoder" "12.6.2" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + tslib "^2.6.2" + +"@polkadot/wasm-bridge@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz#e1b01906b19e06cbca3d94f10f5666f2ae0baadc" + integrity sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g== + dependencies: + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto-asmjs@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz#c6d41bc4b48b5359d57a24ca3066d239f2d70a34" + integrity sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q== + dependencies: + tslib "^2.6.2" + +"@polkadot/wasm-crypto-init@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz#7e1fe79ba978fb0a4a0f74a92d976299d38bc4b8" + integrity sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g== + dependencies: + "@polkadot/wasm-bridge" "7.3.2" + "@polkadot/wasm-crypto-asmjs" "7.3.2" + "@polkadot/wasm-crypto-wasm" "7.3.2" + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto-wasm@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz#44e08ed5cf6499ce4a3aa7247071a5d01f6a74f4" + integrity sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw== + dependencies: + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz#61bbcd9e591500705c8c591e6aff7654bdc8afc9" + integrity sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw== + dependencies: + "@polkadot/wasm-bridge" "7.3.2" + "@polkadot/wasm-crypto-asmjs" "7.3.2" + "@polkadot/wasm-crypto-init" "7.3.2" + "@polkadot/wasm-crypto-wasm" "7.3.2" + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-util@7.3.2", "@polkadot/wasm-util@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz#4fe6370d2b029679b41a5c02cd7ebf42f9b28de1" + integrity sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-bigint@12.6.2", "@polkadot/x-bigint@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz#59b7a615f205ae65e1ac67194aefde94d3344580" + integrity sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-fetch@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz#b1bca028db90263bafbad2636c18d838d842d439" + integrity sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw== + dependencies: + "@polkadot/x-global" "12.6.2" + node-fetch "^3.3.2" + tslib "^2.6.2" + +"@polkadot/x-global@12.6.2", "@polkadot/x-global@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-12.6.2.tgz#31d4de1c3d4c44e4be3219555a6d91091decc4ec" + integrity sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-randomvalues@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz#13fe3619368b8bf5cb73781554859b5ff9d900a2" + integrity sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textdecoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz#b86da0f8e8178f1ca31a7158257e92aea90b10e4" + integrity sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-textencoder@12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz#81d23bd904a2c36137a395c865c5fefa21abfb44" + integrity sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + +"@polkadot/x-ws@^12.6.2": + version "12.6.2" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-12.6.2.tgz#b99094d8e53a03be1de903d13ba59adaaabc767a" + integrity sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw== + dependencies: + "@polkadot/x-global" "12.6.2" + tslib "^2.6.2" + ws "^8.15.1" + +"@scure/base@^1.1.1", "@scure/base@^1.1.5": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" + integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== + +"@substrate/connect-extension-protocol@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.0.0.tgz#badaa6e6b5f7c7d56987d778f4944ddb83cd9ea7" + integrity sha512-nKu8pDrE3LNCEgJjZe1iGXzaD6OSIDD4Xzz/yo4KO9mQ6LBvf49BVrt4qxBFGL6++NneLiWUZGoh+VSd4PyVIg== + +"@substrate/connect-known-chains@^1.1.4": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@substrate/connect-known-chains/-/connect-known-chains-1.1.6.tgz#2627d329b82b46c7d745752c48c73e1b8ce6ac81" + integrity sha512-JwtdGbnK3ZqrY1qp3Ifr/p648sp9hG0Q715h4nRghnqZJnMQIiLKaFkcLnvrAiYQD3zNTYDztHidy5Q/u0TcbQ== + +"@substrate/connect@0.8.10": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.8.10.tgz#810b6589f848828aa840c731a1f36b84fe0e5956" + integrity sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w== + dependencies: + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + "@substrate/light-client-extension-helpers" "^0.0.6" + smoldot "2.0.22" + +"@substrate/light-client-extension-helpers@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.6.tgz#bec1c7997241226db50b44ad85a992b4348d21c3" + integrity sha512-girltEuxQ1BvkJWmc8JJlk4ZxnlGXc/wkLcNguhY+UoDEMBK0LsdtfzQKIfrIehi4QdeSBlFEFBoI4RqPmsZzA== + dependencies: + "@polkadot-api/json-rpc-provider" "0.0.1" + "@polkadot-api/json-rpc-provider-proxy" "0.0.1" + "@polkadot-api/observable-client" "0.1.0" + "@polkadot-api/substrate-client" "0.0.1" + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.4" + rxjs "^7.8.1" + +"@substrate/ss58-registry@^1.44.0": + version "1.49.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.49.0.tgz#ed9507316d13f49b2bccb65f08ec97180f71fc39" + integrity sha512-leW6Ix4LD7XgvxT7+aobPWSw+WvPcN2Rxof1rmd0mNC5t2n99k1N7UNEvz7YEFSOUeHWmKIY7F5q8KeIqYoHfA== + +"@types/bn.js@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.8.tgz#45c26a2a5de26c3534a9504530ddb3b27ce031ac" + integrity sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA== + dependencies: + undici-types "~5.26.4" + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^4.1.0: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +mock-socket@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" + integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nock@^13.5.0: + version "13.5.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479" + integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +scale-ts@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/scale-ts/-/scale-ts-1.6.0.tgz#e9641093c5a9e50f964ddb1607139034e3e932e9" + integrity sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q== + +smoldot@2.0.22: + version "2.0.22" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.22.tgz#1e924d2011a31c57416e79a2b97a460f462a31c7" + integrity sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g== + dependencies: + ws "^8.8.1" + +tslib@^2.1.0, tslib@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + +ws@^8.15.1, ws@^8.8.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==