diff --git a/libs/types/src/domain_address.rs b/libs/types/src/domain_address.rs index 55eee21ada..cedc209a4d 100644 --- a/libs/types/src/domain_address.rs +++ b/libs/types/src/domain_address.rs @@ -63,6 +63,12 @@ impl DomainAddress { } } +impl From<(EVMChainId, [u8; 20])> for DomainAddress { + fn from((chain_id, address): (EVMChainId, [u8; 20])) -> Self { + Self::evm(chain_id, address) + } +} + impl From for Domain { fn from(x: DomainAddress) -> Self { match x { diff --git a/libs/types/src/tokens.rs b/libs/types/src/tokens.rs index 824e6a5608..8005aa52bf 100644 --- a/libs/types/src/tokens.rs +++ b/libs/types/src/tokens.rs @@ -23,7 +23,7 @@ use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::{traits::Get, DispatchError, TokenError}; -use crate::{domain_address::DomainAddress, xcm::XcmMetadata, EVMChainId}; +use crate::{xcm::XcmMetadata, EVMChainId}; pub const MAX_ASSET_STRING_LIMIT: u32 = 128; @@ -395,44 +395,6 @@ impl CrossChainTransferability { } } -/// Liquidity Pools-wrapped tokens -/// -/// Currently, LiquidityPools are only deployed on EVM-based chains and -/// therefore we only support EVM tokens. In the far future, we might support -/// wrapped tokens from other chains such as Cosmos based ones. -#[derive( - Clone, - Copy, - PartialOrd, - Ord, - PartialEq, - Eq, - Debug, - Encode, - Decode, - TypeInfo, - MaxEncodedLen, - Serialize, - Deserialize, -)] -pub enum LiquidityPoolsWrappedToken { - /// An EVM-native token - EVM { - /// The EVM chain id where the token is deployed - chain_id: EVMChainId, - /// The token contract address - address: [u8; 20], - }, -} - -impl From for DomainAddress { - fn from(token: LiquidityPoolsWrappedToken) -> Self { - match token { - LiquidityPoolsWrappedToken::EVM { chain_id, address } => Self::EVM(chain_id, address), - } - } -} - #[derive( Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, )] diff --git a/pallets/liquidity-pools/src/lib.rs b/pallets/liquidity-pools/src/lib.rs index 9e450c3411..7d78d97629 100644 --- a/pallets/liquidity-pools/src/lib.rs +++ b/pallets/liquidity-pools/src/lib.rs @@ -109,7 +109,7 @@ pub mod pallet { }; use cfg_types::{ permissions::{PermissionScope, PoolRole, Role}, - tokens::{CustomMetadata, LiquidityPoolsWrappedToken}, + tokens::CustomMetadata, EVMChainId, }; use frame_support::{ @@ -306,7 +306,7 @@ pub mod pallet { /// The metadata of the given asset does not declare it as transferable /// via LiquidityPools'. AssetNotLiquidityPoolsTransferable, - /// The asset is not a [LiquidityPoolsWrappedToken] and thus cannot be + /// The asset is not a a wrapped token and thus cannot be /// transferred via liquidity pools. AssetNotLiquidityPoolsWrappedToken, /// A pool could not be found. @@ -453,14 +453,12 @@ pub mod pallet { .ensure_mul(price)?; // Check that the registered asset location matches the destination - match Self::try_get_wrapped_token(¤cy_id)? { - LiquidityPoolsWrappedToken::EVM { chain_id, .. } => { - ensure!( - Domain::EVM(chain_id) == destination, - Error::::InvalidDomain - ); - } - } + let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?; + ensure!( + Domain::EVM(chain_id) == destination, + Error::::InvalidDomain + ); + let currency = Self::try_get_general_index(currency_id)?; T::OutboundQueue::submit( @@ -614,14 +612,11 @@ pub mod pallet { let currency = Self::try_get_general_index(currency_id)?; // Check that the registered asset location matches the destination - match Self::try_get_wrapped_token(¤cy_id)? { - LiquidityPoolsWrappedToken::EVM { chain_id, .. } => { - ensure!( - Domain::EVM(chain_id) == receiver.domain(), - Error::::InvalidDomain - ); - } - } + let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?; + ensure!( + Domain::EVM(chain_id) == receiver.domain(), + Error::::InvalidDomain + ); T::PreTransferFilter::check((who.clone(), receiver.clone(), currency_id))?; @@ -674,10 +669,7 @@ pub mod pallet { let currency = Self::try_get_general_index(currency_id)?; - let LiquidityPoolsWrappedToken::EVM { - chain_id, - address: evm_address, - } = Self::try_get_wrapped_token(¤cy_id)?; + let (chain_id, evm_address) = Self::try_get_wrapped_token(¤cy_id)?; T::OutboundQueue::submit( who, @@ -872,13 +864,12 @@ pub mod pallet { } /// Checks whether the given currency is transferable via LiquidityPools - /// and whether its metadata contains a location which can be - /// converted to [LiquidityPoolsWrappedToken]. + /// and whether its metadata contains an evm location. /// /// Requires the currency to be registered in the `AssetRegistry`. pub fn try_get_wrapped_token( currency_id: &T::CurrencyId, - ) -> Result { + ) -> Result<(EVMChainId, [u8; 20]), DispatchError> { let meta = T::AssetRegistry::metadata(currency_id).ok_or(Error::::AssetNotFound)?; ensure!( meta.additional.transferability.includes_liquidity_pools(), @@ -904,9 +895,7 @@ pub mod pallet { network: None, key: address, }], - ) if Some(pallet_instance.into()) == pallet_index => { - Ok(LiquidityPoolsWrappedToken::EVM { chain_id, address }) - } + ) if Some(pallet_instance.into()) == pallet_index => Ok((chain_id, address)), _ => Err(Error::::AssetNotLiquidityPoolsWrappedToken.into()), } } @@ -944,8 +933,7 @@ pub mod pallet { let currency = Self::try_get_general_index(currency_id)?; - let LiquidityPoolsWrappedToken::EVM { chain_id, .. } = - Self::try_get_wrapped_token(¤cy_id)?; + let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?; Ok((currency, chain_id)) } diff --git a/pallets/liquidity-pools/src/tests.rs b/pallets/liquidity-pools/src/tests.rs index 588da523d0..2b3d462649 100644 --- a/pallets/liquidity-pools/src/tests.rs +++ b/pallets/liquidity-pools/src/tests.rs @@ -62,7 +62,7 @@ mod util { } } - pub fn wrapped_transferable_metadata() -> AssetMetadata { + pub fn locatable_transferable_metadata() -> AssetMetadata { let pallet_index = PalletInfo::index::(); AssetMetadata { location: Some(VersionedLocation::V4(Location::new( @@ -93,7 +93,7 @@ mod transfer { #[test] fn success() { System::externalities().execute_with(|| { - AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); TransferFilter::mock_check(|_| Ok(())); Tokens::mint_into(CURRENCY_ID, &ALICE, AMOUNT).unwrap(); Gateway::mock_submit(|sender, destination, msg| { @@ -226,7 +226,7 @@ mod transfer { #[test] fn with_wrong_domain() { System::externalities().execute_with(|| { - AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); assert_noop!( LiquidityPools::transfer( @@ -243,7 +243,7 @@ mod transfer { #[test] fn without_satisfy_filter() { System::externalities().execute_with(|| { - AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); TransferFilter::mock_check(|_| Err(DispatchError::Other("Err"))); assert_noop!( @@ -261,7 +261,7 @@ mod transfer { #[test] fn without_sufficient_balance() { System::externalities().execute_with(|| { - AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); TransferFilter::mock_check(|_| Ok(())); assert_noop!( @@ -636,7 +636,7 @@ mod update_token_price { assert_eq!(origin, POOL_CURRENCY_ID); Ok(MARKET_RATIO) }); - AssetRegistry::mock_metadata(|_| Some(util::wrapped_transferable_metadata())); + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); Gateway::mock_submit(|sender, destination, msg| { assert_eq!(sender, ALICE); assert_eq!(destination, EVM_DOMAIN_ADDRESS.domain()); @@ -878,6 +878,86 @@ mod update_member { } } +mod add_currency { + use super::*; + + #[test] + fn success() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::locatable_transferable_metadata())); + Gateway::mock_submit(|sender, destination, msg| { + assert_eq!(sender, ALICE); + assert_eq!(destination, EVM_DOMAIN_ADDRESS.domain()); + assert_eq!( + msg, + Message::AddCurrency { + currency: util::currency_index(CURRENCY_ID), + evm_address: CONTRACT_ACCOUNT, + } + ); + Ok(()) + }); + + assert_ok!(LiquidityPools::add_currency( + RuntimeOrigin::signed(ALICE), + CURRENCY_ID + )); + }) + } + + mod erroring_out { + use super::*; + + #[test] + fn with_no_metadata() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| None); + + assert_noop!( + LiquidityPools::add_currency(RuntimeOrigin::signed(ALICE), CURRENCY_ID), + Error::::AssetNotFound, + ); + }) + } + + #[test] + fn with_unsupported_token() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::default_metadata())); + + assert_noop!( + LiquidityPools::add_currency(RuntimeOrigin::signed(ALICE), CurrencyId::Native), + TokenError::Unsupported, + ); + }) + } + + #[test] + fn with_no_transferible_asset() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::default_metadata())); + + assert_noop!( + LiquidityPools::add_currency(RuntimeOrigin::signed(ALICE), CURRENCY_ID), + Error::::AssetNotLiquidityPoolsTransferable, + ); + }) + } + + #[test] + fn with_wrong_location() { + System::externalities().execute_with(|| { + AssetRegistry::mock_metadata(|_| Some(util::transferable_metadata())); + + assert_noop!( + LiquidityPools::add_currency(RuntimeOrigin::signed(ALICE), CURRENCY_ID), + Error::::AssetNotLiquidityPoolsWrappedToken + ); + }) + } + } +} + #[test] fn receiving_invalid_message() { System::externalities().execute_with(|| { diff --git a/runtime/integration-tests/src/cases/liquidity_pools.rs b/runtime/integration-tests/src/cases/liquidity_pools.rs index da277433a6..43ca11a567 100644 --- a/runtime/integration-tests/src/cases/liquidity_pools.rs +++ b/runtime/integration-tests/src/cases/liquidity_pools.rs @@ -26,10 +26,8 @@ use frame_support::{ use liquidity_pools_gateway_routers::{ DomainRouter, EthereumXCMRouter, XCMRouter, XcmDomain, DEFAULT_PROOF_SIZE, }; -use orml_traits::MultiCurrency; use pallet_investments::CollectOutcome; use pallet_liquidity_pools::Message; -use pallet_liquidity_pools_gateway::Call as LiquidityPoolsGatewayCall; use pallet_pool_system::tranches::{TrancheInput, TrancheLoc, TrancheType}; use runtime_common::{ account_conversion::AccountConverter, foreign_investments::IdentityPoolCurrencyConverter, @@ -126,21 +124,6 @@ mod utils { 10u128.saturating_pow(decimals) } - pub fn set_domain_router_call( - domain: Domain, - router: DomainRouter, - ) -> T::RuntimeCallExt { - LiquidityPoolsGatewayCall::set_domain_router { domain, router }.into() - } - - pub fn add_instance_call(instance: DomainAddress) -> T::RuntimeCallExt { - LiquidityPoolsGatewayCall::add_instance { instance }.into() - } - - pub fn remove_instance_call(instance: DomainAddress) -> T::RuntimeCallExt { - LiquidityPoolsGatewayCall::remove_instance { instance }.into() - } - /// Creates a new pool for for the given id with the provided currency. /// * BOB as admin and depositor /// * Two tranches @@ -680,196 +663,8 @@ mod utils { use utils::*; mod add_allow_upgrade { - use cfg_types::tokens::LiquidityPoolsWrappedToken; - use super::*; - #[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(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - let gateway_sender = ::Sender::get(); - - let currency_id = AUSD_CURRENCY_ID; - - enable_liquidity_pool_transferability::(currency_id); - - assert_eq!( - orml_tokens::Pallet::::free_balance(GLMR_CURRENCY_ID, &gateway_sender), - DEFAULT_BALANCE_GLMR - ); - - assert_ok!(pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id, - )); - - let currency_index = - pallet_liquidity_pools::Pallet::::try_get_general_index(currency_id) - .expect("can get general index for currency"); - - let LiquidityPoolsWrappedToken::EVM { - address: evm_address, - .. - } = pallet_liquidity_pools::Pallet::::try_get_wrapped_token(¤cy_id) - .expect("can get wrapped token"); - - let outbound_message = pallet_liquidity_pools_gateway::OutboundMessageQueue::::get( - T::OutboundMessageNonce::one(), - ) - .expect("expected outbound queue message"); - - assert_eq!( - outbound_message.2, - Message::AddCurrency { - currency: currency_index, - evm_address, - }, - ); - }); - } - - #[test_runtimes([development])] - fn add_currency_should_fail() { - let mut env = FudgeEnv::::from_parachain_storage( - Genesis::default() - .add(genesis::balances::(cfg(1_000))) - .add(genesis::tokens::(vec![( - GLMR_CURRENCY_ID, - DEFAULT_BALANCE_GLMR, - )])) - .storage(), - ); - - setup_test(&mut env); - - env.parachain_state_mut(|| { - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::ForeignAsset(42) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Native - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - CurrencyId::Staking(cfg_types::tokens::StakingCurrency::BlockRewards) - ), - pallet_liquidity_pools::Error::::AssetNotFound - ); - - // Should fail to add currency_id which is missing a registered - // Location - let currency_id = CurrencyId::ForeignAsset(100); - - assert_ok!(orml_asset_registry::Pallet::::register_asset( - ::RuntimeOrigin::root(), - AssetMetadata { - name: BoundedVec::default(), - symbol: BoundedVec::default(), - decimals: 12, - location: None, - existential_deposit: 1_000_000, - additional: CustomMetadata { - transferability: CrossChainTransferability::LiquidityPools, - mintable: false, - permissioned: false, - pool_currency: false, - local_representation: None, - }, - }, - Some(currency_id) - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsWrappedToken - ); - - // Add convertable Location to metadata but remove transferability - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - // Changed: Add multilocation to metadata for some random EVM chain id for - // which no instance is registered - Some(Some(liquidity_pools_transferable_multilocation::( - u64::MAX, - [1u8; 20], - ))), - Some(CustomMetadata { - // Changed: Disallow liquidityPools transferability - transferability: CrossChainTransferability::Xcm(Default::default()), - ..Default::default() - }), - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - - // Switch transferability from XCM to None - assert_ok!(orml_asset_registry::Pallet::::update_asset( - ::RuntimeOrigin::root(), - currency_id, - None, - None, - None, - None, - None, - Some(CustomMetadata { - // Changed: Disallow cross chain transferability entirely - transferability: CrossChainTransferability::None, - ..Default::default() - }) - )); - - assert_noop!( - pallet_liquidity_pools::Pallet::::add_currency( - RawOrigin::Signed(Keyring::Bob.into()).into(), - currency_id - ), - pallet_liquidity_pools::Error::::AssetNotLiquidityPoolsTransferable - ); - }); - } - #[test_runtimes([development])] fn allow_investment_currency() { let mut env = FudgeEnv::::from_parachain_storage(