diff --git a/Cargo.lock b/Cargo.lock index 2473dec47..2c91928f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4671,7 +4671,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "234.0.0" +version = "235.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -11544,7 +11544,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.21.4" +version = "1.21.5" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 1b3a1a9e9..adffc4d86 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.21.4" +version = "1.21.5" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/cross_chain_transfer.rs b/integration-tests/src/cross_chain_transfer.rs index ef40e57a1..d85915c89 100644 --- a/integration-tests/src/cross_chain_transfer.rs +++ b/integration-tests/src/cross_chain_transfer.rs @@ -6,14 +6,25 @@ use frame_support::{assert_noop, assert_ok}; use polkadot_xcm::{latest::prelude::*, v3::WeightLimit, VersionedMultiAssets, VersionedXcm}; use cumulus_primitives_core::ParaId; +use frame_support::dispatch::GetDispatchInfo; +use frame_support::storage::with_transaction; +use frame_support::traits::OnInitialize; use frame_support::weights::Weight; use hex_literal::hex; +use hydradx_runtime::AssetRegistry; use hydradx_traits::registry::Mutate; +use hydradx_traits::AssetKind; +use hydradx_traits::Create; use orml_traits::currency::MultiCurrency; use pretty_assertions::assert_eq; use primitives::AccountId; +use sp_core::Decode; use sp_core::H256; +use sp_runtime::traits::ConstU32; use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; +use sp_runtime::DispatchResult; +use sp_runtime::FixedU128; +use sp_runtime::TransactionOutcome; use xcm_emulator::TestExt; // Determine the hash for assets expected to be have been trapped. @@ -419,7 +430,7 @@ fn assets_should_be_trapped_when_assets_are_unknown() { .into(), pallet_relaychain_info::Event::CurrentBlockNumbers { parachain_block_number: 3, - relaychain_block_number: 7, + relaychain_block_number: 8, } .into(), ]); @@ -460,6 +471,401 @@ fn claim_trapped_asset_should_work() { }); } +#[test] +fn transfer_foreign_asset_from_asset_hub_to_hydra_should_work() { + //Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + + add_currency_price(FOREIGN_ASSET, FixedU128::from(1)); + + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + }); + + AssetHub::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + + assert_ok!(hydradx_runtime::Tokens::deposit( + FOREIGN_ASSET, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + let foreign_asset = MultiAsset::from(( + MultiLocation { + parents: 2, + interior: Junctions::X1(GlobalConsensus(NetworkId::BitcoinCash)), + }, + 100 * UNITS, + )); + + let bob_beneficiary: MultiLocation = Junction::AccountId32 { id: BOB, network: None }.into(); + + let xcm = + xcm_for_deposit_reserve_asset_to_hydra::(foreign_asset, bob_beneficiary); + + //Act + let res = hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_parts(399_600_000_000, 0), + ); + assert_ok!(res); + + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); + }); + + //Assert + Hydra::execute_with(|| { + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::Success { .. } + )) + )); + let fee = hydradx_runtime::Tokens::free_balance(FOREIGN_ASSET, &hydradx_runtime::Treasury::account_id()); + assert!(fee > 0, "treasury should have received fees"); + + //Check if the foreign asset from Assethub has been deposited successfully + assert_eq!( + hydradx_runtime::Currencies::free_balance(FOREIGN_ASSET, &AccountId::from(BOB)), + 100 * UNITS + ); + }); +} + +#[test] +fn transfer_foreign_asset_from_acala_to_hydra_should_not_work() { + //Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + + add_currency_price(FOREIGN_ASSET, FixedU128::from(1)); + + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + }); + + Acala::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + + assert_ok!(hydradx_runtime::Tokens::deposit( + FOREIGN_ASSET, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + let foreign_asset = MultiAsset::from(( + MultiLocation { + parents: 2, + interior: Junctions::X1(GlobalConsensus(NetworkId::BitcoinCash)), + }, + 100 * UNITS, + )); + + let bob_beneficiary: MultiLocation = Junction::AccountId32 { id: BOB, network: None }.into(); + + let xcm = + xcm_for_deposit_reserve_asset_to_hydra::(foreign_asset, bob_beneficiary); + + //Act + let res = hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_parts(399_600_000_000, 0), + ); + assert_ok!(res); + + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); + }); + + //Assert + Hydra::execute_with(|| { + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::Fail { .. } + )) + )); + }); +} + +#[test] +fn transfer_dot_reserve_from_asset_hub_to_hydra_should_not_work() { + //Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + DOT, + hydradx_runtime::AssetLocation(MultiLocation::new(1, Here)) + )); + + add_currency_price(FOREIGN_ASSET, FixedU128::from(1)); + add_currency_price(DOT, FixedU128::from(1)); + + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + }); + + AssetHub::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + register_dot(); + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + + assert_ok!(hydradx_runtime::Tokens::deposit( + FOREIGN_ASSET, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + assert_ok!(hydradx_runtime::Tokens::deposit( + DOT, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + let dot = MultiAsset::from(( + MultiLocation { + parents: 1, + interior: Junctions::Here, + }, + 100 * UNITS, + )); + + let bob_beneficiary: MultiLocation = Junction::AccountId32 { id: BOB, network: None }.into(); + + let xcm = xcm_for_deposit_reserve_asset_to_hydra::(dot, bob_beneficiary); + + //Act + let res = hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_parts(399_600_000_000, 0), + ); + assert_ok!(res); + + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); + }); + + //Assert + Hydra::execute_with(|| { + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::Fail { .. } + )) + )); + }); +} + +#[test] +fn transfer_dot_reserve_from_non_asset_hub_chain_to_hydra_should_not_work() { + //Arrange + TestNet::reset(); + + Hydra::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + DOT, + hydradx_runtime::AssetLocation(MultiLocation::new(1, Here)) + )); + + add_currency_price(FOREIGN_ASSET, FixedU128::from(1)); + add_currency_price(DOT, FixedU128::from(1)); + + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + }); + + Acala::execute_with(|| { + let _ = with_transaction(|| { + register_foreign_asset(); + register_dot(); + TransactionOutcome::Commit(DispatchResult::Ok(())) + }); + + assert_ok!(hydradx_runtime::Tokens::deposit( + FOREIGN_ASSET, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + assert_ok!(hydradx_runtime::Tokens::deposit( + DOT, + &AccountId::from(ALICE), + 3000 * UNITS + )); + + let dot = MultiAsset::from(( + MultiLocation { + parents: 1, + interior: Junctions::Here, + }, + 100 * UNITS, + )); + + let bob_beneficiary: MultiLocation = Junction::AccountId32 { id: BOB, network: None }.into(); + + let xcm = xcm_for_deposit_reserve_asset_to_hydra::(dot, bob_beneficiary); + + //Act + let res = hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_parts(399_600_000_000, 0), + ); + assert_ok!(res); + + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); + }); + + //Assert + Hydra::execute_with(|| { + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::Fail { .. } + )) + )); + }); +} + +fn xcm_for_deposit_reserve_asset_to_hydra( + assets: MultiAsset, + beneficiary: MultiLocation, +) -> VersionedXcm { + use polkadot_runtime::xcm_config::BaseXcmWeight; + use xcm_builder::FixedWeightBounds; + use xcm_executor::traits::WeightBounds; + + type Weigher = FixedWeightBounds>; + + let dest = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); + + let context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); + + let fee_asset = assets.clone().reanchored(&dest, context).expect("should reanchor"); + let weight_limit = { + let fees = fee_asset.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited::(vec![assets.clone()].into()), + ClearOrigin, + BuyExecution { + fees, + weight_limit: Limited(Weight::zero()), + }, + DepositAsset { + assets: Definite(assets.clone().into()), + beneficiary, + }, + ]); + // use local weight for remote message and hope for the best. + let remote_weight = Weigher::weight(&mut remote_message).expect("weighing should not fail"); + Limited(remote_weight) + }; + + // executed on local (AssetHub) + let message = Xcm(vec![ + WithdrawAsset(vec![fee_asset.clone(), assets.clone().into()].into()), + DepositReserveAsset { + assets: Definite(vec![fee_asset.clone(), assets.clone().into()].into()), + dest, + xcm: Xcm(vec![ + // executed on remote (on hydra) + BuyExecution { + fees: fee_asset, + weight_limit, + }, + DepositAsset { + assets: Definite(assets.into()), + beneficiary, + }, + ]), + }, + ]); + + VersionedXcm::V3(message) +} + +fn register_foreign_asset() { + assert_ok!(AssetRegistry::register_sufficient_asset( + Some(FOREIGN_ASSET), + Some(b"FORA".to_vec().try_into().unwrap()), + AssetKind::Token, + 1_000_000, + None, + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 2, + X1(GlobalConsensus(NetworkId::BitcoinCash)) + ))), + None, + )); +} + +fn register_dot() { + assert_ok!(AssetRegistry::register_sufficient_asset( + Some(DOT), + Some(b"DOT".to_vec().try_into().unwrap()), + AssetKind::Token, + 1_000_000, + None, + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new(1, Here))), + None, + )); +} + +fn add_currency_price(asset_id: u32, price: FixedU128) { + assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( + hydradx_runtime::RuntimeOrigin::root(), + asset_id, + price, + )); + + // make sure the price is propagated + hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); +} + fn trap_asset() -> MultiAsset { Acala::execute_with(|| { assert_eq!( @@ -502,7 +908,7 @@ fn trap_asset() -> MultiAsset { .into(), pallet_relaychain_info::Event::CurrentBlockNumbers { parachain_block_number: 3, - relaychain_block_number: 7, + relaychain_block_number: 8, } .into(), ]); diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 019d6ae53..650aa10f0 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -14,6 +14,7 @@ use orml_traits::currency::MultiCurrency; use polkadot_xcm::{latest::prelude::*, VersionedXcm}; use pretty_assertions::assert_eq; use primitives::constants::chain::CORE_ASSET_ID; +use primitives::AccountId; use sp_runtime::traits::{Convert, Zero}; use sp_runtime::DispatchResult; use sp_runtime::{FixedU128, Permill, TransactionOutcome}; diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index 31905fb8f..218dcc53a 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -60,6 +60,7 @@ pub fn to_ether(b: Balance) -> Balance { pub const UNITS: Balance = 1_000_000_000_000; +pub const ASSET_HUB_PARA_ID: u32 = 1_000; pub const ACALA_PARA_ID: u32 = 2_000; pub const HYDRA_PARA_ID: u32 = 2_034; pub const MOONBEAM_PARA_ID: u32 = 2_004; @@ -86,6 +87,7 @@ pub const ETH: AssetId = 4; pub const BTC: AssetId = 5; pub const ACA: AssetId = 6; pub const WETH: AssetId = 20; +pub const FOREIGN_ASSET: AssetId = 21; pub const PEPE: AssetId = 420; pub const INSUFFICIENT_ASSET: AssetId = 500; @@ -183,7 +185,24 @@ decl_test_parachains! { PolkadotXcm: hydradx_runtime::PolkadotXcm, Balances: hydradx_runtime::Balances, } - } + }, + pub struct AssetHub { + genesis = para::genesis(ASSET_HUB_PARA_ID), + on_init = { + hydradx_runtime::System::set_block_number(1); + }, + runtime = hydradx_runtime, + core = { + XcmpMessageHandler: hydradx_runtime::XcmpQueue, + DmpMessageHandler: hydradx_runtime::DmpQueue, + LocationToAccountId: hydradx_runtime::xcm::LocationToAccountId, + ParachainInfo: hydradx_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: hydradx_runtime::PolkadotXcm, + Balances: hydradx_runtime::Balances, + } + }, } decl_test_networks! { @@ -194,6 +213,7 @@ decl_test_networks! { Moonbeam, Interlay, Hydra, + AssetHub, ], bridge = () }, diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 7ebff30a3..4bc9888e0 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "234.0.0" +version = "235.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 6a1747ad2..219d872b2 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -109,7 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 234, + spec_version: 235, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 3289ed7e9..0f7c5d5cd 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -12,7 +12,7 @@ use cumulus_primitives_core::ParaId; use frame_support::{ parameter_types, sp_runtime::traits::{AccountIdConversion, Convert}, - traits::{ConstU32, Contains, Everything, Get, Nothing}, + traits::{ConstU32, Contains, ContainsPair, Everything, Get, Nothing}, PalletId, }; use frame_system::EnsureRoot; @@ -99,8 +99,35 @@ parameter_types! { pub const MaxNumberOfInstructions: u16 = 100; pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); + + pub AssetHubLocation: MultiLocation = (Parent, Parachain(1000)).into(); +} + +/// Matches foreign assets from a given origin. +/// Foreign assets are assets bridged from other consensus systems. i.e parents > 1. +pub struct IsForeignNativeAssetFrom(PhantomData); +impl ContainsPair for IsForeignNativeAssetFrom +where + Origin: Get, +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let loc = Origin::get(); + &loc == origin + && matches!( + asset, + MultiAsset { + id: Concrete(MultiLocation { parents: 2, .. }), + fun: Fungible(_), + }, + ) + } } +pub type Reserves = ( + IsForeignNativeAssetFrom, + MultiNativeAsset, +); + pub struct XcmConfig; impl Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -108,7 +135,7 @@ impl Config for XcmConfig { type AssetTransactor = LocalAssetTransactor; type OriginConverter = XcmOriginToCallOrigin; - type IsReserve = MultiNativeAsset; + type IsReserve = Reserves; type IsTeleporter = (); // disabled type UniversalLocation = UniversalLocation;