diff --git a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs index 382b2ff9de..553118e96b 100644 --- a/runtime/integration-tests/src/generic/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/generic/cases/liquidity_pools.rs @@ -6063,6 +6063,460 @@ mod development { ); } } + + mod transfers { + use super::*; + + 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), + ), + orml_tokens::Error::::BalanceTooLow + ); + + assert_ok!(pallet_liquidity_pools::Pallet::::transfer( + RawOrigin::Signed(source_account.into()).into(), + currency_id, + dest_address.clone(), + amount, + )); + + // The account to which the currency should have been transferred + // to on Centrifuge for bookkeeping purposes. + let domain_account: AccountId = Domain::convert(dest_address.domain()); + // Verify that the correct amount of the token was transferred + // to the dest domain account on Centrifuge. + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &domain_account), + amount + ); + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &source_account.into()), + initial_balance - amount + ); + }); + } + + fn transfer_non_tranche_tokens_to_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 amount = DEFAULT_BALANCE_GLMR / 2; + let currency_id = AUSD_CURRENCY_ID; + let receiver: AccountId = Keyring::Bob.into(); + + // Mock incoming decrease message + let msg = LiquidityPoolMessage::Transfer { + currency: general_currency_index::(currency_id), + // sender is irrelevant for other -> local + sender: Keyring::Alice.into(), + receiver: receiver.clone().into(), + amount, + }; + + assert_eq!(orml_tokens::Pallet::::total_issuance(currency_id), 0); + + // Finally, verify that we can now transfer the tranche to the destination + // address + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + + // Verify that the correct amount was minted + assert_eq!( + orml_tokens::Pallet::::total_issuance(currency_id), + amount + ); + assert_eq!( + orml_tokens::Pallet::::free_balance(currency_id, &receiver), + amount + ); + + // Verify empty transfers throw + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + LiquidityPoolMessage::Transfer { + currency: general_currency_index::(currency_id), + sender: Keyring::Alice.into(), + receiver: receiver.into(), + amount: 0, + }, + ), + pallet_liquidity_pools::Error::::InvalidTransferAmount + ); + }); + } + + fn transfer_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 pool_id = DEFAULT_POOL_ID; + let amount = 100_000; + let dest_address: DomainAddress = DomainAddress::EVM(1284, [99; 20]); + let receiver = Keyring::Bob; + + // Create the pool + create_ausd_pool::(pool_id); + + let tranche_tokens: CurrencyId = cfg_types::tokens::TrancheCurrency::generate( + pool_id, + default_tranche_id::(pool_id), + ) + .into(); + + // Verify that we first need the destination address to be whitelisted + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( + RawOrigin::Signed(Keyring::Alice.into()).into(), + pool_id, + default_tranche_id::(pool_id), + dest_address.clone(), + amount, + ), + pallet_liquidity_pools::Error::::UnauthorizedTransfer + ); + + // Make receiver the MembersListAdmin of this Pool + assert_ok!(pallet_permissions::Pallet::::add( + ::RuntimeOrigin::root(), + Role::PoolRole(PoolRole::PoolAdmin), + receiver.into(), + PermissionScope::Pool(pool_id), + Role::PoolRole(PoolRole::InvestorAdmin), + )); + + // Whitelist destination as TrancheInvestor of this Pool + let valid_until = u64::MAX; + assert_ok!(pallet_permissions::Pallet::::add( + RawOrigin::Signed(receiver.into()).into(), + Role::PoolRole(PoolRole::InvestorAdmin), + AccountConverter::::convert(dest_address.clone()), + PermissionScope::Pool(pool_id), + Role::PoolRole(PoolRole::TrancheInvestor( + default_tranche_id::(pool_id), + valid_until + )), + )); + + // Call the pallet_liquidity_pools::Pallet::::update_member which ensures the + // destination address is whitelisted. + assert_ok!(pallet_liquidity_pools::Pallet::::update_member( + RawOrigin::Signed(receiver.into()).into(), + pool_id, + default_tranche_id::(pool_id), + dest_address.clone(), + valid_until, + )); + + // Give receiver enough Tranche balance to be able to transfer it + assert_ok!(orml_tokens::Pallet::::deposit( + tranche_tokens, + &receiver.into(), + amount + )); + + // Finally, verify that we can now transfer the tranche to the destination + // address + assert_ok!( + pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( + RawOrigin::Signed(receiver.into()).into(), + pool_id, + default_tranche_id::(pool_id), + dest_address.clone(), + amount, + ) + ); + + // The account to which the tranche should have been transferred + // to on Centrifuge for bookkeeping purposes. + let domain_account: AccountId = Domain::convert(dest_address.domain()); + + // Verify that the correct amount of the Tranche token was transferred + // to the dest domain account on Centrifuge. + assert_eq!( + orml_tokens::Pallet::::free_balance(tranche_tokens, &domain_account), + amount + ); + assert!( + orml_tokens::Pallet::::free_balance(tranche_tokens, &receiver.into()) + .is_zero() + ); + }); + } + + fn transfer_tranche_tokens_to_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(|| { + // Create new pool + let pool_id = DEFAULT_POOL_ID; + create_ausd_pool::(pool_id); + + let amount = 100_000_000; + let receiver: AccountId = Keyring::Bob.into(); + let sender: DomainAddress = DomainAddress::EVM(1284, [99; 20]); + let sending_domain_locator = + Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); + let tranche_id = default_tranche_id::(pool_id); + let tranche_tokens: CurrencyId = + cfg_types::tokens::TrancheCurrency::generate(pool_id, tranche_id).into(); + let valid_until = u64::MAX; + + // Fund `DomainLocator` account of origination domain tranche tokens are + // transferred from this account instead of minting + assert_ok!(orml_tokens::Pallet::::mint_into( + tranche_tokens, + &sending_domain_locator, + amount + )); + + // Mock incoming decrease message + let msg = LiquidityPoolMessage::TransferTrancheTokens { + pool_id, + tranche_id, + sender: sender.address(), + domain: Domain::Centrifuge, + receiver: receiver.clone().into(), + amount, + }; + + // Verify that we first need the receiver to be whitelisted + assert_noop!( + pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg.clone() + ), + pallet_liquidity_pools::Error::::UnauthorizedTransfer + ); + + // Make receiver the MembersListAdmin of this Pool + assert_ok!(pallet_permissions::Pallet::::add( + ::RuntimeOrigin::root(), + Role::PoolRole(PoolRole::PoolAdmin), + receiver.clone(), + PermissionScope::Pool(pool_id), + Role::PoolRole(PoolRole::InvestorAdmin), + )); + + // Whitelist destination as TrancheInvestor of this Pool + assert_ok!(pallet_permissions::Pallet::::add( + RawOrigin::Signed(receiver.clone()).into(), + Role::PoolRole(PoolRole::InvestorAdmin), + receiver.clone(), + PermissionScope::Pool(pool_id), + Role::PoolRole(PoolRole::TrancheInvestor( + default_tranche_id::(pool_id), + valid_until + )), + )); + + // Finally, verify that we can now transfer the tranche to the destination + // address + assert_ok!(pallet_liquidity_pools::Pallet::::submit( + DEFAULT_DOMAIN_ADDRESS_MOONBEAM, + msg + )); + + // Verify that the correct amount of the Tranche token was transferred + // to the dest domain account on Centrifuge. + assert_eq!( + orml_tokens::Pallet::::free_balance(tranche_tokens, &receiver), + amount + ); + assert!(orml_tokens::Pallet::::free_balance( + tranche_tokens, + &sending_domain_locator + ) + .is_zero()); + }); + } + + /// Try to transfer tranches for non-existing pools or invalid tranche + /// ids for existing pools. + fn transferring_invalid_tranche_tokens_should_fail() { + 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 dest_address: DomainAddress = DomainAddress::EVM(1284, [99; 20]); + + let valid_pool_id: u64 = 42; + create_ausd_pool::(valid_pool_id); + let valid_tranche_id = default_tranche_id::(valid_pool_id); + let valid_until = u64::MAX; + let transfer_amount = 42; + let invalid_pool_id = valid_pool_id + 1; + let invalid_tranche_id = valid_tranche_id.map(|i| i.saturating_add(1)); + assert!(pallet_pool_system::Pallet::::pool(invalid_pool_id).is_none()); + + // Make Keyring::Bob the MembersListAdmin of both pools + assert_ok!(pallet_permissions::Pallet::::add( + ::RuntimeOrigin::root(), + Role::PoolRole(PoolRole::PoolAdmin), + Keyring::Bob.into(), + PermissionScope::Pool(valid_pool_id), + Role::PoolRole(PoolRole::InvestorAdmin), + )); + assert_ok!(pallet_permissions::Pallet::::add( + ::RuntimeOrigin::root(), + Role::PoolRole(PoolRole::PoolAdmin), + Keyring::Bob.into(), + PermissionScope::Pool(invalid_pool_id), + Role::PoolRole(PoolRole::InvestorAdmin), + )); + + // Give Keyring::Bob investor role for (valid_pool_id, invalid_tranche_id) and + // (invalid_pool_id, valid_tranche_id) + assert_ok!(pallet_permissions::Pallet::::add( + RawOrigin::Signed(Keyring::Bob.into()).into(), + Role::PoolRole(PoolRole::InvestorAdmin), + AccountConverter::::convert(dest_address.clone()), + PermissionScope::Pool(invalid_pool_id), + Role::PoolRole(PoolRole::TrancheInvestor(valid_tranche_id, valid_until)), + )); + assert_ok!(pallet_permissions::Pallet::::add( + RawOrigin::Signed(Keyring::Bob.into()).into(), + Role::PoolRole(PoolRole::InvestorAdmin), + AccountConverter::::convert(dest_address.clone()), + PermissionScope::Pool(valid_pool_id), + Role::PoolRole(PoolRole::TrancheInvestor(invalid_tranche_id, valid_until)), + )); + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( + RawOrigin::Signed(Keyring::Bob.into()).into(), + invalid_pool_id, + valid_tranche_id, + dest_address.clone(), + transfer_amount + ), + pallet_liquidity_pools::Error::::PoolNotFound + ); + assert_noop!( + pallet_liquidity_pools::Pallet::::transfer_tranche_tokens( + RawOrigin::Signed(Keyring::Bob.into()).into(), + valid_pool_id, + invalid_tranche_id, + dest_address, + transfer_amount + ), + pallet_liquidity_pools::Error::::TrancheNotFound + ); + }); + } + + crate::test_for_runtimes!([development], transfer_non_tranche_tokens_from_local); + crate::test_for_runtimes!([development], transfer_non_tranche_tokens_to_local); + crate::test_for_runtimes!([development], transfer_tranche_tokens_from_local); + crate::test_for_runtimes!([development], transfer_tranche_tokens_to_local); + crate::test_for_runtimes!( + [development], + transferring_invalid_tranche_tokens_should_fail + ); + } } mod altair { diff --git a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/mod.rs b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/mod.rs deleted file mode 100644 index a51147aadd..0000000000 --- a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 Centrifuge GmbH (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. - -// Copyright 2021 Centrifuge GmbH (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. - -pub(crate) mod setup; -mod transfers; - -#[test] -fn test_vec_to_fixed_array() { - let src = "TrNcH".as_bytes().to_vec(); - let symbol: [u8; 32] = cfg_utils::vec_to_fixed_array(src); - - assert!(symbol.starts_with("TrNcH".as_bytes())); - - assert_eq!( - symbol, - [ - 84, 114, 78, 99, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0 - ] - ); -} - -// Verify that the max tranche token symbol and name lengths are what the -// LiquidityPools pallet expects. -#[test] -fn verify_tranche_fields_sizes() { - assert_eq!( - cfg_types::consts::pools::MaxTrancheNameLengthBytes::get(), - pallet_liquidity_pools::TOKEN_NAME_SIZE as u32 - ); - assert_eq!( - cfg_types::consts::pools::MaxTrancheSymbolLengthBytes::get(), - pallet_liquidity_pools::TOKEN_SYMBOL_SIZE as u32 - ); -} diff --git a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/setup.rs b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/setup.rs deleted file mode 100644 index ca329c7c4b..0000000000 --- a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/setup.rs +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2021 Centrifuge GmbH (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. - -// Copyright 2021 Centrifuge GmbH (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 cfg_primitives::{currency_decimals, Balance, PoolId, TrancheId}; -use cfg_traits::{investments::InvestmentAccountant, PoolMutate, Seconds}; -use cfg_types::{ - domain_address::{Domain, DomainAddress}, - fixed_point::{Quantity, Rate}, - pools::TrancheMetadata, - tokens::{CrossChainTransferability, CurrencyId, CustomMetadata}, -}; -use cumulus_primitives_core::Junction::GlobalConsensus; -use frame_support::{ - assert_ok, - traits::{ - fungible::Mutate as _, - fungibles::{Balanced, Mutate}, - Get, PalletInfo, - }, - weights::Weight, -}; -use fudge::primitives::Chain; -use liquidity_pools_gateway_routers::{ - ethereum_xcm::EthereumXCMRouter, DomainRouter, XCMRouter, XcmDomain as GatewayXcmDomain, - XcmTransactInfo, DEFAULT_PROOF_SIZE, -}; -use orml_asset_registry::{AssetMetadata, Metadata}; -use orml_traits::MultiCurrency; -use pallet_liquidity_pools::Message; -use pallet_pool_system::tranches::{TrancheInput, TrancheType}; -use polkadot_parachain::primitives::Id; -use runtime_common::{ - account_conversion::AccountConverter, xcm::general_key, xcm_fees::default_per_second, -}; -use sp_core::H160; -use sp_runtime::{ - traits::{AccountIdConversion, BadOrigin, ConstU32, Convert, EnsureAdd, One, Zero}, - BoundedVec, DispatchError, Perquintill, SaturatedConversion, WeakBoundedVec, -}; -use xcm::{ - prelude::{Parachain, X1, X2, X3, XCM_VERSION}, - v3::{Junction, Junction::*, Junctions, MultiLocation, NetworkId}, - VersionedMultiLocation, -}; - -use crate::{ - chain::{ - centrifuge::{ - LiquidityPools, LiquidityPoolsGateway, OrmlAssetRegistry, OrmlTokens, PolkadotXcm, - PoolSystem, Runtime as DevelopmentRuntime, RuntimeOrigin, Tokens, TreasuryPalletId, - PARA_ID, - }, - relay::{Hrmp as RelayHrmp, RuntimeOrigin as RelayRuntimeOrigin}, - }, - liquidity_pools::pallet::development::{setup::dollar, tests::register_ausd}, - utils::{ - accounts::Keyring, - env::{TestEnv, PARA_ID_SIBLING}, - AUSD_CURRENCY_ID, GLMR_CURRENCY_ID, GLMR_ED, MOONBEAM_EVM_CHAIN_ID, - }, -}; - -// 10 GLMR (18 decimals) -pub const DEFAULT_BALANCE_GLMR: Balance = 10_000_000_000_000_000_000; -pub const DOMAIN_MOONBEAM: Domain = Domain::EVM(MOONBEAM_EVM_CHAIN_ID); -pub const DEFAULT_EVM_ADDRESS_MOONBEAM: [u8; 20] = [99; 20]; -pub const DEFAULT_DOMAIN_ADDRESS_MOONBEAM: DomainAddress = - DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, DEFAULT_EVM_ADDRESS_MOONBEAM); -pub const DEFAULT_VALIDITY: Seconds = 2555583502; -pub const DEFAULT_OTHER_DOMAIN_ADDRESS: DomainAddress = - DomainAddress::EVM(MOONBEAM_EVM_CHAIN_ID, [0; 20]); -pub const DEFAULT_POOL_ID: u64 = 42; -pub const DEFAULT_SIBLING_LOCATION: MultiLocation = MultiLocation { - parents: 1, - interior: X1(Parachain(PARA_ID_SIBLING)), -}; - -pub type LiquidityPoolMessage = Message; - -pub fn get_default_moonbeam_native_token_location() -> MultiLocation { - MultiLocation { - parents: 1, - interior: X2(Parachain(PARA_ID_SIBLING), general_key(&[0, 1])), - } -} - -pub fn set_test_domain_router( - evm_chain_id: u64, - xcm_domain_location: VersionedMultiLocation, - currency_id: CurrencyId, -) { - let ethereum_xcm_router = EthereumXCMRouter:: { - router: XCMRouter { - xcm_domain: GatewayXcmDomain { - 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(), - }, - _marker: Default::default(), - }; - - let domain_router = DomainRouter::EthereumXCM(ethereum_xcm_router); - let domain = Domain::EVM(evm_chain_id); - - assert_ok!(LiquidityPoolsGateway::set_domain_router( - RuntimeOrigin::root(), - domain, - domain_router, - )); -} - -pub fn setup_test_env(env: &mut TestEnv) { - env.with_mut_state(Chain::Para(PARA_ID), || { - setup_pre_requirements(); - }) - .unwrap(); - - env.with_mut_state(Chain::Relay, || { - setup_hrmp_channel(); - }) - .unwrap(); - - env.evolve().unwrap(); -} - -/// Initializes universally required storage for liquidityPools tests: -/// * Set the EthereumXCM router which in turn sets: -/// * transact info and domain router for Moonbeam `MultiLocation`, -/// * fee for GLMR (`GLMR_CURRENCY_ID`), -/// * Register GLMR and AUSD in `OrmlAssetRegistry`, -/// * Mint 10 GLMR (`DEFAULT_BALANCE_GLMR`) for the LP Gateway Sender. -/// * Set the XCM version for the sibling parachain. -/// -/// NOTE: AUSD is the default pool currency in `create_pool`. -/// Neither AUSD nor GLMR are registered as a liquidityPools-transferable -/// currency! -pub fn setup_pre_requirements() { - /// Set the EthereumXCM router necessary for Moonbeam. - set_test_domain_router( - MOONBEAM_EVM_CHAIN_ID, - DEFAULT_SIBLING_LOCATION.into(), - GLMR_CURRENCY_ID, - ); - - /// Register Moonbeam's native token - assert_ok!(OrmlAssetRegistry::register_asset( - RuntimeOrigin::root(), - asset_metadata( - "Glimmer".into(), - "GLMR".into(), - 18, - false, - GLMR_ED, - Some(VersionedMultiLocation::V3( - get_default_moonbeam_native_token_location() - )), - CrossChainTransferability::Xcm(Default::default()), - ), - Some(GLMR_CURRENCY_ID) - )); - - // Fund the gateway sender account with enough glimmer to pay for fees - assert_ok!(Tokens::set_balance( - RuntimeOrigin::root(), - ::Sender::get().into(), - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - 0, - )); - - // Register AUSD in the asset registry which is the default pool currency in - // `create_pool` - register_ausd(); - - // Set the XCM version used when sending XCM messages to sibling. - assert_ok!(PolkadotXcm::force_xcm_version( - RuntimeOrigin::root(), - Box::new(MultiLocation::new( - 1, - Junctions::X1(Junction::Parachain(PARA_ID_SIBLING)), - )), - XCM_VERSION, - )); -} - -/// Opens the required HRMP channel between parachain and sibling. -/// -/// NOTE - this is should be done on the relay chain. -pub fn setup_hrmp_channel() { - assert_ok!(RelayHrmp::force_open_hrmp_channel( - RelayRuntimeOrigin::root(), - Id::from(PARA_ID), - Id::from(PARA_ID_SIBLING), - 10, - 1024, - )); - - assert_ok!(RelayHrmp::force_process_hrmp_open( - RelayRuntimeOrigin::root(), - 0, - )); -} - -/// 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, dollar(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!(PoolSystem::create( - Keyring::Bob.into(), - Keyring::Bob.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, - cfg_types::consts::pools::MaxTrancheNameLengthBytes, - >::try_from("A highly advanced tranche".as_bytes().to_vec()) - .expect(""), - token_symbol: BoundedVec::< - u8, - cfg_types::consts::pools::MaxTrancheSymbolLengthBytes, - >::try_from("TrNcH".as_bytes().to_vec()) - .expect(""), - } - }, - 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(), - } - } - ], - currency_id, - currency_decimals, - )); -} - -/// Returns a `VersionedMultiLocation` 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], -) -> VersionedMultiLocation { - VersionedMultiLocation::V3(MultiLocation { - parents: 0, - interior: X3( - PalletInstance( - ::PalletInfo::index::() - .expect("LiquidityPools should have pallet index") - .saturated_into(), - ), - GlobalConsensus(NetworkId::Ethereum { chain_id }), - AccountKey20 { - network: None, - key: address, - }, - ), - }) -} - -/// 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 = - 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!(OrmlAssetRegistry::update_asset( - RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - location, - Some(CustomMetadata { - // Changed: Allow liquidity_pools transferability - transferability: CrossChainTransferability::LiquidityPools, - ..metadata.additional - }) - )); -} - -/// Returns metadata for the given data with existential deposit of -/// 1_000_000. -pub fn asset_metadata( - name: Vec, - symbol: Vec, - decimals: u32, - is_pool_currency: bool, - existential_deposit: Balance, - location: Option, - transferability: CrossChainTransferability, -) -> AssetMetadata { - AssetMetadata { - name, - symbol, - decimals, - location, - existential_deposit, - additional: CustomMetadata { - transferability, - mintable: false, - permissioned: false, - pool_currency: is_pool_currency, - }, - } -} - -pub(crate) mod investments { - use cfg_primitives::AccountId; - use cfg_traits::investments::TrancheCurrency as TrancheCurrencyT; - use cfg_types::investments::InvestmentAccount; - use development_runtime::{OrderBook, PoolSystem}; - use pallet_pool_system::tranches::TrancheLoc; - - use super::*; - - /// 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() - } - - /// 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_investment_id() -> cfg_types::tokens::TrancheCurrency { - ::TrancheCurrency::generate( - DEFAULT_POOL_ID, - default_tranche_id(DEFAULT_POOL_ID), - ) - } - - /// Returns the tranche id at index 0 for the given pool id. - pub fn default_tranche_id(pool_id: u64) -> TrancheId { - let pool_details = PoolSystem::pool(pool_id).expect("Pool should exist"); - pool_details - .tranches - .tranche_id(TrancheLoc::Index(0)) - .expect("Tranche at index 0 exists") - } - - /// 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") - } -} diff --git a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs b/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs deleted file mode 100644 index d95e21ce2b..0000000000 --- a/runtime/integration-tests/src/liquidity_pools/pallet/development/tests/liquidity_pools/transfers.rs +++ /dev/null @@ -1,486 +0,0 @@ -// Copyright 2021 Centrifuge GmbH (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. - -// Copyright 2021 Centrifuge GmbH (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 cfg_primitives::{AccountId, Balance, PoolId, TrancheId, CFG}; -use cfg_traits::{ - investments::{OrderManager, TrancheCurrency as TrancheCurrencyT}, - liquidity_pools::InboundQueue, - Permissions as _, -}; -use cfg_types::{ - domain_address::{Domain, DomainAddress}, - permissions::{PermissionScope, PoolRole, Role}, - tokens::{ - CrossChainTransferability, CurrencyId, CurrencyId::ForeignAsset, CustomMetadata, - ForeignAssetId, - }, -}; -use frame_support::{assert_noop, assert_ok, dispatch::Weight, traits::fungibles::Mutate}; -use fudge::primitives::Chain; -use orml_traits::{asset_registry::AssetMetadata, FixedConversionRateProvider, MultiCurrency}; -use runtime_common::account_conversion::AccountConverter; -use sp_runtime::{ - traits::{Convert, One, Zero}, - BoundedVec, DispatchError, Storage, -}; -use tokio::runtime::Handle; -use xcm::{latest::MultiLocation, VersionedMultiLocation}; - -use crate::{ - chain::centrifuge::{ - LiquidityPools, LocationToAccountId, OrmlTokens, Permissions, PoolSystem, - Runtime as DevelopmentRuntime, RuntimeOrigin, System, PARA_ID, - }, - liquidity_pools::pallet::development::{ - setup::dollar, - tests::liquidity_pools::setup::{ - asset_metadata, create_ausd_pool, create_currency_pool, - enable_liquidity_pool_transferability, - investments::{default_tranche_id, general_currency_index, investment_id}, - liquidity_pools_transferable_multilocation, setup_test_env, LiquidityPoolMessage, - DEFAULT_BALANCE_GLMR, DEFAULT_DOMAIN_ADDRESS_MOONBEAM, DEFAULT_POOL_ID, - }, - }, - utils::{accounts::Keyring, env, genesis, AUSD_CURRENCY_ID, AUSD_ED, MOONBEAM_EVM_CHAIN_ID}, -}; - -#[tokio::test] -async fn transfer_non_tranche_tokens_from_local() { - let mut env = { - let mut genesis = Storage::default(); - genesis::default_native_balances::(&mut genesis); - env::test_env_with_centrifuge_storage(Handle::current(), genesis) - }; - - setup_test_env(&mut env); - - env.with_mut_state(Chain::Para(PARA_ID), || { - 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!( - OrmlTokens::free_balance(currency_id, &source_account.into()), - 0 - ); - assert_ok!(OrmlTokens::mint_into( - currency_id, - &source_account.into(), - initial_balance - )); - assert_eq!( - OrmlTokens::free_balance(currency_id, &source_account.into()), - initial_balance - ); - - // Only `ForeignAsset` can be transferred - assert_noop!( - LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.into()), - CurrencyId::Tranche(42u64, [0u8; 16]), - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::InvalidTransferCurrency - ); - assert_noop!( - LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.into()), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards), - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.into()), - CurrencyId::Native, - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - - // Cannot transfer as long as cross chain transferability is disabled - assert_noop!( - LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.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!( - LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.into()), - currency_id, - dest_address.clone(), - initial_balance.saturating_add(1), - ), - orml_tokens::Error::::BalanceTooLow - ); - - assert_ok!(LiquidityPools::transfer( - RuntimeOrigin::signed(source_account.into()), - currency_id, - dest_address.clone(), - amount, - )); - - // The account to which the currency should have been transferred - // to on Centrifuge for bookkeeping purposes. - let domain_account: AccountId = Domain::convert(dest_address.domain()); - // Verify that the correct amount of the token was transferred - // to the dest domain account on Centrifuge. - assert_eq!( - OrmlTokens::free_balance(currency_id, &domain_account), - amount - ); - assert_eq!( - OrmlTokens::free_balance(currency_id, &source_account.into()), - initial_balance - amount - ); - }); -} - -#[tokio::test] -async fn transfer_non_tranche_tokens_to_local() { - let mut env = { - let mut genesis = Storage::default(); - genesis::default_native_balances::(&mut genesis); - env::test_env_with_centrifuge_storage(Handle::current(), genesis) - }; - - setup_test_env(&mut env); - - env.with_mut_state(Chain::Para(PARA_ID), || { - let initial_balance = DEFAULT_BALANCE_GLMR; - let amount = DEFAULT_BALANCE_GLMR / 2; - let dest_address = DEFAULT_DOMAIN_ADDRESS_MOONBEAM; - let currency_id = AUSD_CURRENCY_ID; - let receiver: AccountId = Keyring::Bob.into(); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::Transfer { - currency: general_currency_index(currency_id), - // sender is irrelevant for other -> local - sender: Keyring::Alice.into(), - receiver: receiver.clone().into(), - amount, - }; - - assert_eq!(OrmlTokens::total_issuance(currency_id), 0); - - // Finally, verify that we can now transfer the tranche to the destination - // address - assert_ok!(LiquidityPools::submit(DEFAULT_DOMAIN_ADDRESS_MOONBEAM, msg)); - - // Verify that the correct amount was minted - assert_eq!(OrmlTokens::total_issuance(currency_id), amount); - assert_eq!(OrmlTokens::free_balance(currency_id, &receiver), amount); - - // Verify empty transfers throw - assert_noop!( - LiquidityPools::submit( - DEFAULT_DOMAIN_ADDRESS_MOONBEAM, - LiquidityPoolMessage::Transfer { - currency: general_currency_index(currency_id), - sender: Keyring::Alice.into(), - receiver: receiver.into(), - amount: 0, - }, - ), - pallet_liquidity_pools::Error::::InvalidTransferAmount - ); - }); -} - -#[tokio::test] -async fn transfer_tranche_tokens_from_local() { - let mut env = { - let mut genesis = Storage::default(); - genesis::default_balances::(&mut genesis); - env::test_env_with_centrifuge_storage(Handle::current(), genesis) - }; - - setup_test_env(&mut env); - - env.with_mut_state(Chain::Para(PARA_ID), || { - let pool_id = DEFAULT_POOL_ID; - let amount = 100_000; - let dest_address: DomainAddress = DomainAddress::EVM(1284, [99; 20]); - let receiver = Keyring::Bob; - - // Create the pool - create_ausd_pool(pool_id); - - let tranche_tokens: CurrencyId = - cfg_types::tokens::TrancheCurrency::generate(pool_id, default_tranche_id(pool_id)) - .into(); - - // Verify that we first need the destination address to be whitelisted - assert_noop!( - LiquidityPools::transfer_tranche_tokens( - RuntimeOrigin::signed(Keyring::Alice.into()), - pool_id, - default_tranche_id(pool_id), - dest_address.clone(), - amount, - ), - pallet_liquidity_pools::Error::::UnauthorizedTransfer - ); - - // Make receiver the MembersListAdmin of this Pool - assert_ok!(Permissions::add( - RuntimeOrigin::root(), - Role::PoolRole(PoolRole::PoolAdmin), - receiver.into(), - PermissionScope::Pool(pool_id), - Role::PoolRole(PoolRole::InvestorAdmin), - )); - - // Whitelist destination as TrancheInvestor of this Pool - let valid_until = u64::MAX; - assert_ok!(Permissions::add( - RuntimeOrigin::signed(receiver.into()), - Role::PoolRole(PoolRole::InvestorAdmin), - AccountConverter::::convert( - dest_address.clone() - ), - PermissionScope::Pool(pool_id), - Role::PoolRole(PoolRole::TrancheInvestor( - default_tranche_id(pool_id), - valid_until - )), - )); - - // Call the LiquidityPools::update_member which ensures the destination address - // is whitelisted. - assert_ok!(LiquidityPools::update_member( - RuntimeOrigin::signed(receiver.into()), - pool_id, - default_tranche_id(pool_id), - dest_address.clone(), - valid_until, - )); - - // Give receiver enough Tranche balance to be able to transfer it - OrmlTokens::deposit(tranche_tokens, &receiver.into(), amount); - - // Finally, verify that we can now transfer the tranche to the destination - // address - assert_ok!(LiquidityPools::transfer_tranche_tokens( - RuntimeOrigin::signed(receiver.into()), - pool_id, - default_tranche_id(pool_id), - dest_address.clone(), - amount, - )); - - // The account to which the tranche should have been transferred - // to on Centrifuge for bookkeeping purposes. - let domain_account: AccountId = Domain::convert(dest_address.domain()); - - // Verify that the correct amount of the Tranche token was transferred - // to the dest domain account on Centrifuge. - assert_eq!( - OrmlTokens::free_balance(tranche_tokens, &domain_account), - amount - ); - assert!(OrmlTokens::free_balance(tranche_tokens, &receiver.into()).is_zero()); - }); -} - -#[tokio::test] -async fn transfer_tranche_tokens_to_local() { - let mut env = { - let mut genesis = Storage::default(); - genesis::default_balances::(&mut genesis); - env::test_env_with_centrifuge_storage(Handle::current(), genesis) - }; - - setup_test_env(&mut env); - - env.with_mut_state(Chain::Para(PARA_ID), || { - // Create new pool - let pool_id = DEFAULT_POOL_ID; - create_ausd_pool(pool_id); - - let amount = 100_000_000; - let receiver: AccountId = Keyring::Bob.into(); - let sender: DomainAddress = DomainAddress::EVM(1284, [99; 20]); - let sending_domain_locator = Domain::convert(DEFAULT_DOMAIN_ADDRESS_MOONBEAM.domain()); - let tranche_id = default_tranche_id(pool_id); - let tranche_tokens: CurrencyId = - cfg_types::tokens::TrancheCurrency::generate(pool_id, tranche_id).into(); - let valid_until = u64::MAX; - - // Fund `DomainLocator` account of origination domain tranche tokens are - // transferred from this account instead of minting - assert_ok!(OrmlTokens::mint_into( - tranche_tokens, - &sending_domain_locator, - amount - )); - - // Mock incoming decrease message - let msg = LiquidityPoolMessage::TransferTrancheTokens { - pool_id, - tranche_id, - sender: sender.address(), - domain: Domain::Centrifuge, - receiver: receiver.clone().into(), - amount, - }; - - // Verify that we first need the receiver to be whitelisted - assert_noop!( - LiquidityPools::submit(DEFAULT_DOMAIN_ADDRESS_MOONBEAM, msg.clone()), - pallet_liquidity_pools::Error::::UnauthorizedTransfer - ); - - // Make receiver the MembersListAdmin of this Pool - assert_ok!(Permissions::add( - RuntimeOrigin::root(), - Role::PoolRole(PoolRole::PoolAdmin), - receiver.clone(), - PermissionScope::Pool(pool_id), - Role::PoolRole(PoolRole::InvestorAdmin), - )); - - // Whitelist destination as TrancheInvestor of this Pool - assert_ok!(Permissions::add( - RuntimeOrigin::signed(receiver.clone()), - Role::PoolRole(PoolRole::InvestorAdmin), - receiver.clone(), - PermissionScope::Pool(pool_id), - Role::PoolRole(PoolRole::TrancheInvestor( - default_tranche_id(pool_id), - valid_until - )), - )); - - // Finally, verify that we can now transfer the tranche to the destination - // address - assert_ok!(LiquidityPools::submit(DEFAULT_DOMAIN_ADDRESS_MOONBEAM, msg)); - - // Verify that the correct amount of the Tranche token was transferred - // to the dest domain account on Centrifuge. - assert_eq!(OrmlTokens::free_balance(tranche_tokens, &receiver), amount); - assert!(OrmlTokens::free_balance(tranche_tokens, &sending_domain_locator).is_zero()); - }); -} - -/// Try to transfer tranches for non-existing pools or invalid tranche ids for -/// existing pools. -#[tokio::test] -async fn transferring_invalid_tranche_tokens_should_fail() { - let mut env = { - let mut genesis = Storage::default(); - genesis::default_balances::(&mut genesis); - env::test_env_with_centrifuge_storage(Handle::current(), genesis) - }; - - setup_test_env(&mut env); - - env.with_mut_state(Chain::Para(PARA_ID), || { - let dest_address: DomainAddress = DomainAddress::EVM(1284, [99; 20]); - - let valid_pool_id: u64 = 42; - create_ausd_pool(valid_pool_id); - let valid_tranche_id = default_tranche_id(valid_pool_id); - let valid_until = u64::MAX; - let transfer_amount = 42; - let invalid_pool_id = valid_pool_id + 1; - let invalid_tranche_id = valid_tranche_id.map(|i| i.saturating_add(1)); - assert!(PoolSystem::pool(invalid_pool_id).is_none()); - - // Make Keyring::Bob the MembersListAdmin of both pools - assert_ok!(Permissions::add( - RuntimeOrigin::root(), - Role::PoolRole(PoolRole::PoolAdmin), - Keyring::Bob.into(), - PermissionScope::Pool(valid_pool_id), - Role::PoolRole(PoolRole::InvestorAdmin), - )); - assert_ok!(Permissions::add( - RuntimeOrigin::root(), - Role::PoolRole(PoolRole::PoolAdmin), - Keyring::Bob.into(), - PermissionScope::Pool(invalid_pool_id), - Role::PoolRole(PoolRole::InvestorAdmin), - )); - - // Give Keyring::Bob investor role for (valid_pool_id, invalid_tranche_id) and - // (invalid_pool_id, valid_tranche_id) - assert_ok!(Permissions::add( - RuntimeOrigin::signed(Keyring::Bob.into()), - Role::PoolRole(PoolRole::InvestorAdmin), - AccountConverter::::convert( - dest_address.clone() - ), - PermissionScope::Pool(invalid_pool_id), - Role::PoolRole(PoolRole::TrancheInvestor(valid_tranche_id, valid_until)), - )); - assert_ok!(Permissions::add( - RuntimeOrigin::signed(Keyring::Bob.into()), - Role::PoolRole(PoolRole::InvestorAdmin), - AccountConverter::::convert( - dest_address.clone() - ), - PermissionScope::Pool(valid_pool_id), - Role::PoolRole(PoolRole::TrancheInvestor(invalid_tranche_id, valid_until)), - )); - assert_noop!( - LiquidityPools::transfer_tranche_tokens( - RuntimeOrigin::signed(Keyring::Bob.into()), - invalid_pool_id, - valid_tranche_id, - dest_address.clone(), - transfer_amount - ), - pallet_liquidity_pools::Error::::PoolNotFound - ); - assert_noop!( - LiquidityPools::transfer_tranche_tokens( - RuntimeOrigin::signed(Keyring::Bob.into()), - valid_pool_id, - invalid_tranche_id, - dest_address, - transfer_amount - ), - pallet_liquidity_pools::Error::::TrancheNotFound - ); - }); -}