From d1626123f456855593c3a3047e14ffe739241b4e Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Fri, 2 Jun 2023 14:48:24 +0200 Subject: [PATCH 01/73] add integation test for exchange asset --- integration-tests/Cargo.toml | 3 + integration-tests/src/exchange_asset.rs | 128 ++++++++++++++++++++++++ integration-tests/src/lib.rs | 1 + 3 files changed, 132 insertions(+) create mode 100644 integration-tests/src/exchange_asset.rs diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 19d406ee6..fd9ea6aa5 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -175,4 +175,7 @@ std = [ "pallet-authorship/std", "hydradx-traits/std", "pallet-relaychain-info/std", + "polkadot-runtime/std", + "hydradx-runtime/std", + "common-runtime/std", ] diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs new file mode 100644 index 000000000..0a5d13180 --- /dev/null +++ b/integration-tests/src/exchange_asset.rs @@ -0,0 +1,128 @@ +#![cfg(test)] + +use crate::polkadot_test_net::*; +use cumulus_primitives_core::ParaId; +use frame_support::weights::Weight; +use frame_support::{ + assert_ok, + pallet_prelude::*, + sp_runtime::{FixedU128, Permill}, + traits::Contains, +}; +use hex_literal::hex; +use orml_traits::currency::MultiCurrency; +use polkadot_xcm::{latest::prelude::*, v3::WeightLimit, VersionedMultiAssets, VersionedXcm}; +use pretty_assertions::assert_eq; +use sp_core::H256; +use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; +use xcm_emulator::TestExt; + +use frame_support::dispatch::GetDispatchInfo; + +fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>(give: M, want: M) -> VersionedXcm { + use polkadot_runtime::xcm_config::BaseXcmWeight; + use sp_runtime::traits::ConstU32; + use xcm_builder::FixedWeightBounds; + use xcm_executor::traits::WeightBounds; + + type Weigher = FixedWeightBounds>; + + let dest = Parachain(HYDRA_PARA_ID).into(); + let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); + let assets = give.into(); + let max_assets = assets.len() as u32; + let context = GlobalConsensus(NetworkId::Polkadot).into(); + let fees = assets + .get(0) + .expect("should have at least 1 asset") + .clone() + .reanchored(&dest, context) + .expect("should reanchor"); + let weight_limit = { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited::(assets.clone()), + ClearOrigin, + BuyExecution { + fees, + weight_limit: Limited(Weight::zero()), + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + 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) + }; + let give = assets.clone().into(); + let want = want.into(); + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + ExchangeAsset { + give, + want, + maximal: true, + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]); + let message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + TransferReserveAsset { assets, dest, xcm }, + ]); + VersionedXcm::V3(message) +} + +#[test] +fn hydra_should_swap_assets_when_receiving_from_relay() { + //Arrange + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::set_location( + hydradx_runtime::RuntimeOrigin::root(), + 1, + hydradx_runtime::AssetLocation(MultiLocation::parent()) + )); + }); + + Acala::execute_with(|| { + let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( + MultiAsset::from((Here, 300 * UNITS)), + MultiAsset::from((Here, 300 * UNITS)), + ); + //Act + assert_ok!(hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::MAX, + )); + // assert_ok!(polkadot_runtime::XcmPallet::reserve_transfer_assets( + // polkadot_runtime::RuntimeOrigin::signed(ALICE.into()), + // Box::new(Parachain(HYDRA_PARA_ID).into_versioned()), + // Box::new(Junction::AccountId32 { id: BOB, network: None }.into()), + // Box::new((Here, 300 * UNITS).into()), + // 0, + // )); + + //Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(&ParaId::from(HYDRA_PARA_ID).into_account_truncating()), + 310 * UNITS + ); + }); + + let fees = 400641025641; + Hydra::execute_with(|| { + assert_eq!( + hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), + BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees + ); + assert_eq!( + hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), + fees + ); + }); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index a39bd5cbc..a5187943c 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -4,6 +4,7 @@ mod cross_chain_transfer; mod dca; mod dust; mod dust_removal_whitelist; +mod exchange_asset; mod non_native_fee; mod omnipool_init; mod omnipool_liquidity_mining; From 1911994da9929c8823ed73663ef5c7454c8386df Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 5 Jun 2023 11:59:34 +0200 Subject: [PATCH 02/73] adjust first test with proper assertions --- integration-tests/src/exchange_asset.rs | 37 ++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 0a5d13180..e73a22093 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -27,7 +27,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( type Weigher = FixedWeightBounds>; - let dest = Parachain(HYDRA_PARA_ID).into(); + let dest = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); let assets = give.into(); let max_assets = assets.len() as u32; @@ -38,6 +38,8 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( .clone() .reanchored(&dest, context) .expect("should reanchor"); + let give: MultiAssetFilter = assets.clone().into(); + let want = want.into(); let weight_limit = { let fees = fees.clone(); let mut remote_message = Xcm(vec![ @@ -47,6 +49,11 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( fees, weight_limit: Limited(Weight::zero()), }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: true, + }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, @@ -56,8 +63,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let remote_weight = Weigher::weight(&mut remote_message).expect("weighing should not fail"); Limited(remote_weight) }; - let give = assets.clone().into(); - let want = want.into(); + let xcm = Xcm(vec![ BuyExecution { fees, weight_limit }, ExchangeAsset { @@ -90,31 +96,30 @@ fn hydra_should_swap_assets_when_receiving_from_relay() { Acala::execute_with(|| { let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( - MultiAsset::from((Here, 300 * UNITS)), + MultiAsset::from((GeneralIndex(0), 100 * UNITS)), MultiAsset::from((Here, 300 * UNITS)), ); //Act - assert_ok!(hydradx_runtime::PolkadotXcm::execute( + let res = hydradx_runtime::PolkadotXcm::execute( hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), Box::new(xcm), Weight::MAX, - )); - // assert_ok!(polkadot_runtime::XcmPallet::reserve_transfer_assets( - // polkadot_runtime::RuntimeOrigin::signed(ALICE.into()), - // Box::new(Parachain(HYDRA_PARA_ID).into_versioned()), - // Box::new(Junction::AccountId32 { id: BOB, network: None }.into()), - // Box::new((Here, 300 * UNITS).into()), - // 0, - // )); + ); + assert_ok!(res); //Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE_ON_OTHER_PARACHAIN - 100 * UNITS + ); + //TODO: continue here, probably some other error with conversion, debug it assert_eq!( hydradx_runtime::Balances::free_balance(&ParaId::from(HYDRA_PARA_ID).into_account_truncating()), - 310 * UNITS + 100 * UNITS ); }); - let fees = 400641025641; + /*let fees = 400641025641; Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), @@ -124,5 +129,5 @@ fn hydra_should_swap_assets_when_receiving_from_relay() { hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), fees ); - }); + });*/ } From 77a1994be93466080a6513efb5e528468d2fb117 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 12 Jun 2023 15:11:24 +0200 Subject: [PATCH 03/73] reset emulator at the start of the test --- integration-tests/src/exchange_asset.rs | 50 ++++++++++++++++--------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index e73a22093..557440325 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -49,11 +49,11 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( fees, weight_limit: Limited(Weight::zero()), }, - ExchangeAsset { - give: give.clone(), - want: want.clone(), - maximal: true, - }, + // ExchangeAsset { + // give: give.clone(), + // want: want.clone(), + // maximal: true, + // }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, @@ -66,11 +66,11 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let xcm = Xcm(vec![ BuyExecution { fees, weight_limit }, - ExchangeAsset { - give, - want, - maximal: true, - }, + // ExchangeAsset { + // give, + // want, + // maximal: true, + // }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, @@ -84,8 +84,11 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( } #[test] -fn hydra_should_swap_assets_when_receiving_from_relay() { +fn hydra_should_swap_assets_when_receiving_from_acala() { //Arrange + TestNet::reset(); + + dbg!("before hydra 1"); Hydra::execute_with(|| { assert_ok!(hydradx_runtime::AssetRegistry::set_location( hydradx_runtime::RuntimeOrigin::root(), @@ -93,8 +96,11 @@ fn hydra_should_swap_assets_when_receiving_from_relay() { hydradx_runtime::AssetLocation(MultiLocation::parent()) )); }); + dbg!("after hydra 1"); + dbg!("before acala"); Acala::execute_with(|| { + dbg!("execute acala"); let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( MultiAsset::from((GeneralIndex(0), 100 * UNITS)), MultiAsset::from((Here, 300 * UNITS)), @@ -113,21 +119,29 @@ fn hydra_should_swap_assets_when_receiving_from_relay() { ALICE_INITIAL_NATIVE_BALANCE_ON_OTHER_PARACHAIN - 100 * UNITS ); //TODO: continue here, probably some other error with conversion, debug it - assert_eq!( - hydradx_runtime::Balances::free_balance(&ParaId::from(HYDRA_PARA_ID).into_account_truncating()), - 100 * UNITS - ); + // assert_eq!( + // hydradx_runtime::Balances::free_balance(&ParaId::from(HYDRA_PARA_ID).into_account_truncating()), + // 100 * UNITS + // ); + expect_hydra_events(vec![ + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash: Some([9, 0, 8, 33, 54, 22, 19, 20, 3, 152, 149, 31, 97, 142, 128, 167, 186, 128, 27, 162, 115, 18, 183, 5, 49, 22, 165, 146, 39, 125, 144, 142]) } + .into(), + pallet_xcm::Event::Attempted(Outcome::Complete(Weight::from_parts(200000000, 0))).into() + ]); + dbg!("end execute acala"); }); + dbg!("after acala"); - /*let fees = 400641025641; + let fees = 400641025641; + dbg!("before hydra 2"); Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), - BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees + BOB_INITIAL_NATIVE_BALANCE + 100 * UNITS - fees ); assert_eq!( hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), fees ); - });*/ + }); } From 81b48077bf6d75342c211a92644b761029e2f625 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 12 Jun 2023 15:11:49 +0200 Subject: [PATCH 04/73] formatting --- integration-tests/src/exchange_asset.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 557440325..104ea80c2 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -124,9 +124,14 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { // 100 * UNITS // ); expect_hydra_events(vec![ - cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash: Some([9, 0, 8, 33, 54, 22, 19, 20, 3, 152, 149, 31, 97, 142, 128, 167, 186, 128, 27, 162, 115, 18, 183, 5, 49, 22, 165, 146, 39, 125, 144, 142]) } + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { + message_hash: Some([ + 9, 0, 8, 33, 54, 22, 19, 20, 3, 152, 149, 31, 97, 142, 128, 167, 186, 128, 27, 162, 115, 18, 183, + 5, 49, 22, 165, 146, 39, 125, 144, 142, + ]), + } .into(), - pallet_xcm::Event::Attempted(Outcome::Complete(Weight::from_parts(200000000, 0))).into() + pallet_xcm::Event::Attempted(Outcome::Complete(Weight::from_parts(200000000, 0))).into(), ]); dbg!("end execute acala"); }); From f0cc170365182526fc00d4737785739ff1d38d39 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 12 Jun 2023 16:30:28 +0200 Subject: [PATCH 05/73] fix test configuration --- integration-tests/src/exchange_asset.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 104ea80c2..b54f25422 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -31,12 +31,16 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); let assets = give.into(); let max_assets = assets.len() as u32; - let context = GlobalConsensus(NetworkId::Polkadot).into(); + //let context = GlobalConsensus(NetworkId::Polkadot).into(); + let context2 = X2( + GlobalConsensus(NetworkId::Polkadot).into(), + Parachain(ACALA_PARA_ID).into(), + ); let fees = assets .get(0) .expect("should have at least 1 asset") .clone() - .reanchored(&dest, context) + .reanchored(&dest, context2) .expect("should reanchor"); let give: MultiAssetFilter = assets.clone().into(); let want = want.into(); @@ -49,6 +53,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( fees, weight_limit: Limited(Weight::zero()), }, + //TODO: continue here, enable exchange asset and do the implemnetation // ExchangeAsset { // give: give.clone(), // want: want.clone(), @@ -93,7 +98,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { assert_ok!(hydradx_runtime::AssetRegistry::set_location( hydradx_runtime::RuntimeOrigin::root(), 1, - hydradx_runtime::AssetLocation(MultiLocation::parent()) + hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) )); }); dbg!("after hydra 1"); @@ -109,7 +114,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { let res = hydradx_runtime::PolkadotXcm::execute( hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), Box::new(xcm), - Weight::MAX, + Weight::from_ref_time(399_600_000_000), ); assert_ok!(res); @@ -126,8 +131,8 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { expect_hydra_events(vec![ cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash: Some([ - 9, 0, 8, 33, 54, 22, 19, 20, 3, 152, 149, 31, 97, 142, 128, 167, 186, 128, 27, 162, 115, 18, 183, - 5, 49, 22, 165, 146, 39, 125, 144, 142, + 84, 179, 56, 30, 36, 240, 28, 224, 172, 85, 182, 195, 124, 147, 197, 229, 78, 67, 68, 120, 111, + 149, 154, 18, 5, 199, 220, 121, 77, 201, 5, 213, ]), } .into(), From edc9a2bd7019205c6afe55b1f379bbe9b6467ac3 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 13 Jun 2023 14:34:26 +0200 Subject: [PATCH 06/73] add OmniExchanger and failing test, make integration test fail --- Cargo.lock | 1 + integration-tests/src/exchange_asset.rs | 59 ++++++++++----------- runtime/hydradx/Cargo.toml | 1 + runtime/hydradx/src/xcm.rs | 70 ++++++++++++++++++++++++- 4 files changed, 99 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 320bddcba..ec9e199a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3863,6 +3863,7 @@ dependencies = [ "orml-tokens", "orml-traits", "orml-unknown-tokens", + "orml-utilities", "orml-vesting", "orml-xcm", "orml-xcm-support", diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index b54f25422..81ff0d29d 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -29,7 +29,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let dest = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); - let assets = give.into(); + let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32; //let context = GlobalConsensus(NetworkId::Polkadot).into(); let context2 = X2( @@ -42,7 +42,8 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( .clone() .reanchored(&dest, context2) .expect("should reanchor"); - let give: MultiAssetFilter = assets.clone().into(); + // TODO: reanchor + let give: MultiAssetFilter = Definite(give.into()); let want = want.into(); let weight_limit = { let fees = fees.clone(); @@ -53,12 +54,11 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( fees, weight_limit: Limited(Weight::zero()), }, - //TODO: continue here, enable exchange asset and do the implemnetation - // ExchangeAsset { - // give: give.clone(), - // want: want.clone(), - // maximal: true, - // }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: true, + }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, @@ -68,19 +68,20 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let remote_weight = Weigher::weight(&mut remote_message).expect("weighing should not fail"); Limited(remote_weight) }; - + // executed on remote (on hydra) let xcm = Xcm(vec![ BuyExecution { fees, weight_limit }, - // ExchangeAsset { - // give, - // want, - // maximal: true, - // }, + ExchangeAsset { + give, + want, + maximal: true, + }, DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, }, ]); + // executed on local (acala) let message = Xcm(vec![ SetFeesMode { jit_withdraw: true }, TransferReserveAsset { assets, dest, xcm }, @@ -107,7 +108,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { Acala::execute_with(|| { dbg!("execute acala"); let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( - MultiAsset::from((GeneralIndex(0), 100 * UNITS)), + MultiAsset::from((GeneralIndex(0), 50 * UNITS)), MultiAsset::from((Here, 300 * UNITS)), ); //Act @@ -123,21 +124,13 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), ALICE_INITIAL_NATIVE_BALANCE_ON_OTHER_PARACHAIN - 100 * UNITS ); - //TODO: continue here, probably some other error with conversion, debug it - // assert_eq!( - // hydradx_runtime::Balances::free_balance(&ParaId::from(HYDRA_PARA_ID).into_account_truncating()), - // 100 * UNITS - // ); - expect_hydra_events(vec![ - cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { - message_hash: Some([ - 84, 179, 56, 30, 36, 240, 28, 224, 172, 85, 182, 195, 124, 147, 197, 229, 78, 67, 68, 120, 111, - 149, 154, 18, 5, 199, 220, 121, 77, 201, 5, 213, - ]), - } - .into(), - pallet_xcm::Event::Attempted(Outcome::Complete(Weight::from_parts(200000000, 0))).into(), - ]); + // TODO: add utility macro? + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); dbg!("end execute acala"); }); dbg!("after acala"); @@ -147,7 +140,11 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), - BOB_INITIAL_NATIVE_BALANCE + 100 * UNITS - fees + BOB_INITIAL_NATIVE_BALANCE + 50 * UNITS - fees + ); + assert_eq!( + hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), + BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees ); assert_eq!( hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index fa3976bf3..d8821a8ba 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -70,6 +70,7 @@ orml-tokens = { workspace = true } orml-traits = { workspace = true } orml-vesting = { workspace = true } orml-benchmarking = { workspace = true, optional = true } +orml-utilities = { workspace = true } # orml XCM support orml-xtokens = { workspace = true } diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index fdd11e384..6ad0a6625 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -3,6 +3,7 @@ use super::{AssetId, *}; use common_runtime::adapters::ReroutingMultiCurrencyAdapter; use cumulus_primitives_core::ParaId; use frame_support::{ + storage::with_transaction, traits::{Everything, Nothing}, PalletId, }; @@ -21,7 +22,7 @@ use xcm_builder::{ SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; -use xcm_executor::{Config, XcmExecutor}; +use xcm_executor::{traits::AssetExchange, Config, XcmExecutor}; pub type LocalOriginToLocation = SignedToAccountId32; @@ -309,3 +310,70 @@ pub type LocalAssetTransactor = ReroutingMultiCurrencyAdapter< OmnipoolProtocolAccount, TreasuryAccount, >; + +pub struct OmniExchanger; + +impl AssetExchange for OmniExchanger { + fn exchange_asset( + origin: Option<&MultiLocation>, + give: xcm_executor::Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result { + use orml_utilities::with_transaction_result; + use sp_runtime::traits::Convert; + + if maximal { + // sell + let account = if origin.is_none() { + [9; 32] // fake generated account for now + } else { + return Err(give); + }; + let origin = RuntimeOrigin::signed(account.into()); + if give.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset given + if want.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset wanted + let given = give + .fungible_assets_iter() + .next() + .expect("length of 1 checked above; qed"); + // TODO: log errors + let Fungible(amount) = given.fun else { return Err(give) }; + let Some(asset_in) = CurrencyIdConvert::convert(given) else { return Err(give) }; + let Some(wanted) = want.get(0) else { return Err(give) }; + let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; + let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; // TODO: unnecessary clone, maybe? + + with_transaction_result(|| { + // TODO: mint + + Omnipool::sell(origin, asset_in, asset_out, amount, min_buy_amount) + }) + .map_err(|_| give) + .map(|_| todo!("burn and return")) + } else { + // buy + Err(give) // TODO + } + } +} + +mod tests { + use super::*; + use polkadot_xcm::latest::prelude::*; + + const BUY: bool = false; + const SELL: bool = true; + const UNITS: u128 = 1_000_000_000_000; + + #[test] + fn omni_exchanger_exchanges_supported_assets() { + let give = MultiAsset::from((GeneralIndex(0), 50 * UNITS)).into(); + let want = MultiAsset::from((GeneralIndex(1), 100 * UNITS)).into(); + OmniExchanger::exchange_asset(None, give, &want, SELL); + } +} From f2a3690d61ad8d36c9cd28aa0e3876a7a3ba8c01 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 13 Jun 2023 15:44:09 +0200 Subject: [PATCH 07/73] moved xcm exchanger to omnipool because it is omnipool specific --- Cargo.lock | 3 + pallets/omnipool/Cargo.toml | 6 ++ pallets/omnipool/src/lib.rs | 1 + pallets/omnipool/src/tests/mod.rs | 1 + pallets/omnipool/src/tests/xcm_exchange.rs | 63 ++++++++++++++++++++ pallets/omnipool/src/xcm_exchange.rs | 64 +++++++++++++++++++++ runtime/hydradx/src/xcm.rs | 67 ---------------------- 7 files changed, 138 insertions(+), 67 deletions(-) create mode 100644 pallets/omnipool/src/tests/xcm_exchange.rs create mode 100644 pallets/omnipool/src/xcm_exchange.rs diff --git a/Cargo.lock b/Cargo.lock index ec9e199a4..b24d7e46f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7098,6 +7098,7 @@ dependencies = [ "log", "orml-tokens", "orml-traits", + "orml-utilities", "pallet-balances", "parity-scale-codec", "pretty_assertions", @@ -7109,6 +7110,8 @@ dependencies = [ "sp-runtime", "sp-std", "sp-tracing", + "xcm", + "xcm-executor", ] [[package]] diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index 615289bf3..8cc2cb151 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -27,6 +27,7 @@ frame-system = { workspace = true } # ORML orml-traits = { workspace = true } +orml-utilities = { workspace = true } # Warehouse hydradx-traits = { workspace = true } @@ -45,6 +46,10 @@ pallet-balances = { workspace = true, optional = true } sp-core = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } +#Polkadot +xcm-executor = { workspace = true } +polkadot-xcm = { workspace = true } + [dev-dependencies] sp-core = { workspace = true } sp-io = { workspace = true } @@ -69,6 +74,7 @@ std = [ "pallet-balances/std", "orml-tokens/std", "frame-benchmarking/std", + "xcm-executor/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/pallets/omnipool/src/lib.rs b/pallets/omnipool/src/lib.rs index b74e9324e..5e702caab 100644 --- a/pallets/omnipool/src/lib.rs +++ b/pallets/omnipool/src/lib.rs @@ -95,6 +95,7 @@ pub mod router_execution; pub mod traits; pub mod types; pub mod weights; +pub mod xcm_exchange; use crate::traits::{AssetInfo, OmnipoolHooks}; use crate::types::{AssetReserveState, AssetState, Balance, Position, SimpleImbalance, Tradability}; diff --git a/pallets/omnipool/src/tests/mod.rs b/pallets/omnipool/src/tests/mod.rs index 9f070ed26..55cf6ebfb 100644 --- a/pallets/omnipool/src/tests/mod.rs +++ b/pallets/omnipool/src/tests/mod.rs @@ -20,6 +20,7 @@ mod tradability; mod tvl; mod types; mod verification; +mod xcm_exchange; use mock::*; diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs new file mode 100644 index 000000000..2f60d61e6 --- /dev/null +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -0,0 +1,63 @@ +use super::*; +use crate::tests::mock::AssetId as CurrencyId; +use crate::xcm_exchange::OmniExchanger; +use frame_support::{assert_noop, parameter_types}; +use polkadot_xcm::latest::prelude::*; +use pretty_assertions::assert_eq; +use sp_runtime::traits::Convert; +use sp_runtime::Permill; +use sp_runtime::SaturatedConversion; +use xcm_executor::traits::AssetExchange; + +parameter_types! { + pub ExchangeTempAccount: AccountId = 12345; +} + +const BUY: bool = false; +const SELL: bool = true; +const UNITS: u128 = 1_000_000_000_000; + +pub struct CurrencyIdConvert; + +impl Convert> for CurrencyIdConvert { + fn convert(location: MultiLocation) -> Option { + match location { + MultiLocation { + parents: 0, + interior: X1(GeneralIndex(index)), + } => Some(index.saturated_into()), + _ => None, + } + } +} + +impl Convert> for CurrencyIdConvert { + fn convert(asset: MultiAsset) -> Option { + if let MultiAsset { + id: Concrete(location), .. + } = asset + { + Self::convert(location) + } else { + None + } + } +} + +#[test] +fn omni_exchanger_exchanges_supported_assets() { + ExtBuilder::default() + .with_endowed_accounts(vec![ + (Omnipool::protocol_account(), DAI, 1000 * ONE), + (Omnipool::protocol_account(), HDX, NATIVE_AMOUNT), + ]) + .with_initial_pool(FixedU128::from_float(0.5), FixedU128::from(1)) + .build() + .execute_with(|| { + let give = MultiAsset::from((GeneralIndex(DAI.into()), 50 * UNITS)).into(); + let want = MultiAsset::from((GeneralIndex(HDX.into()), 100 * UNITS)).into(); + assert_ok!( + OmniExchanger::::exchange_asset(None, give, &want, SELL) + ); + }); +} diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs new file mode 100644 index 000000000..7a3ed6c58 --- /dev/null +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -0,0 +1,64 @@ +use crate::{Config, Pallet}; +use frame_support::traits::OriginTrait; +use frame_system::pallet_prelude::OriginFor; +use polkadot_xcm::latest::prelude::*; +use sp_core::Get; +use sp_runtime::traits::{AccountIdConversion, Convert}; +use std::marker::PhantomData; +use xcm_executor::traits::AssetExchange; + +pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); + +impl AssetExchange for OmniExchanger +where + T: Config, + TempAccount: Get, + CurrencyIdConvert: Convert>, +{ + fn exchange_asset( + origin: Option<&MultiLocation>, + give: xcm_executor::Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result { + use orml_utilities::with_transaction_result; + use sp_runtime::traits::Convert; + + if maximal { + // sell + let account = if origin.is_none() { + TempAccount::get() + } else { + return Err(give); + }; + let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + if give.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset given + if want.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset wanted + let given = give + .fungible_assets_iter() + .next() + .expect("length of 1 checked above; qed"); + // TODO: log errors + let Fungible(amount) = given.fun else { return Err(give) }; + let Some(asset_in) = CurrencyIdConvert::convert(given) else { return Err(give) }; + let Some(wanted) = want.get(0) else { return Err(give) }; + let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; + let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; // TODO: unnecessary clone, maybe? + + with_transaction_result(|| { + // TODO: mint + + Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount) + }) + .map_err(|_| give) + .map(|_| todo!("burn and return")) + } else { + // buy + Err(give) // TODO + } + } +} diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 6ad0a6625..53e3ec574 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -310,70 +310,3 @@ pub type LocalAssetTransactor = ReroutingMultiCurrencyAdapter< OmnipoolProtocolAccount, TreasuryAccount, >; - -pub struct OmniExchanger; - -impl AssetExchange for OmniExchanger { - fn exchange_asset( - origin: Option<&MultiLocation>, - give: xcm_executor::Assets, - want: &MultiAssets, - maximal: bool, - ) -> Result { - use orml_utilities::with_transaction_result; - use sp_runtime::traits::Convert; - - if maximal { - // sell - let account = if origin.is_none() { - [9; 32] // fake generated account for now - } else { - return Err(give); - }; - let origin = RuntimeOrigin::signed(account.into()); - if give.len() != 1 { - return Err(give); - }; // TODO: we assume only one asset given - if want.len() != 1 { - return Err(give); - }; // TODO: we assume only one asset wanted - let given = give - .fungible_assets_iter() - .next() - .expect("length of 1 checked above; qed"); - // TODO: log errors - let Fungible(amount) = given.fun else { return Err(give) }; - let Some(asset_in) = CurrencyIdConvert::convert(given) else { return Err(give) }; - let Some(wanted) = want.get(0) else { return Err(give) }; - let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; - let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; // TODO: unnecessary clone, maybe? - - with_transaction_result(|| { - // TODO: mint - - Omnipool::sell(origin, asset_in, asset_out, amount, min_buy_amount) - }) - .map_err(|_| give) - .map(|_| todo!("burn and return")) - } else { - // buy - Err(give) // TODO - } - } -} - -mod tests { - use super::*; - use polkadot_xcm::latest::prelude::*; - - const BUY: bool = false; - const SELL: bool = true; - const UNITS: u128 = 1_000_000_000_000; - - #[test] - fn omni_exchanger_exchanges_supported_assets() { - let give = MultiAsset::from((GeneralIndex(0), 50 * UNITS)).into(); - let want = MultiAsset::from((GeneralIndex(1), 100 * UNITS)).into(); - OmniExchanger::exchange_asset(None, give, &want, SELL); - } -} From a7405716c9c25beb08397abed227ec81ecaf9d7e Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 13 Jun 2023 16:18:38 +0200 Subject: [PATCH 08/73] implement sell for OmniExchanger --- pallets/omnipool/src/tests/xcm_exchange.rs | 17 +++++++++++++---- pallets/omnipool/src/xcm_exchange.rs | 18 ++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index 2f60d61e6..c34c83b28 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -46,6 +46,7 @@ impl Convert> for CurrencyIdConvert { #[test] fn omni_exchanger_exchanges_supported_assets() { + // Arrange ExtBuilder::default() .with_endowed_accounts(vec![ (Omnipool::protocol_account(), DAI, 1000 * ONE), @@ -54,10 +55,18 @@ fn omni_exchanger_exchanges_supported_assets() { .with_initial_pool(FixedU128::from_float(0.5), FixedU128::from(1)) .build() .execute_with(|| { - let give = MultiAsset::from((GeneralIndex(DAI.into()), 50 * UNITS)).into(); - let want = MultiAsset::from((GeneralIndex(HDX.into()), 100 * UNITS)).into(); - assert_ok!( + let give = MultiAsset::from((GeneralIndex(DAI.into()), 100 * UNITS)).into(); + let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees + let want = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); + // Act + let received = OmniExchanger::::exchange_asset(None, give, &want, SELL) - ); + .expect("should return ok"); + // Assert + let mut iter = received.fungible_assets_iter(); + let asset_received = iter.next().expect("there should be at least one asset"); + assert!(iter.next().is_none(), "there should only be one asset returned"); + let Fungible(received_amount) = asset_received.fun else { panic!("should be fungible")}; + assert!(received_amount >= wanted_amount); }); } diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index 7a3ed6c58..f28612140 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -21,6 +21,7 @@ where want: &MultiAssets, maximal: bool, ) -> Result { + use orml_traits::MultiCurrency; use orml_utilities::with_transaction_result; use sp_runtime::traits::Convert; @@ -50,12 +51,21 @@ where let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; // TODO: unnecessary clone, maybe? with_transaction_result(|| { - // TODO: mint - - Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount) + T::Currency::deposit(asset_in, &account, amount)?; // mint the incoming tokens + Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount)?; + debug_assert!( + T::Currency::free_balance(asset_in, &account) == 0, + "Sell should not leave any of the incoming asset." + ); + let amount_received = T::Currency::free_balance(asset_out, &account); + debug_assert!( + amount_received >= min_buy_amount, + "Sell should return more than mininum buy amount." + ); + T::Currency::withdraw(asset_out, &account, amount_received); // burn the received tokens + Ok(MultiAsset::from((wanted.id, amount_received)).into()) }) .map_err(|_| give) - .map(|_| todo!("burn and return")) } else { // buy Err(give) // TODO From 326d20906428827a5f0723f91796fa89dc157db2 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 13 Jun 2023 16:23:27 +0200 Subject: [PATCH 09/73] add more asserts and use withdraw result --- pallets/omnipool/src/tests/xcm_exchange.rs | 3 +++ pallets/omnipool/src/xcm_exchange.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index c34c83b28..009307ed9 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -1,5 +1,6 @@ use super::*; use crate::tests::mock::AssetId as CurrencyId; +use crate::tests::mock::{Balances, Tokens}; use crate::xcm_exchange::OmniExchanger; use frame_support::{assert_noop, parameter_types}; use polkadot_xcm::latest::prelude::*; @@ -68,5 +69,7 @@ fn omni_exchanger_exchanges_supported_assets() { assert!(iter.next().is_none(), "there should only be one asset returned"); let Fungible(received_amount) = asset_received.fun else { panic!("should be fungible")}; assert!(received_amount >= wanted_amount); + assert_eq!(Tokens::free_balance(DAI, &ExchangeTempAccount::get()), 0); + assert_eq!(Balances::free_balance(&ExchangeTempAccount::get()), 0); }); } diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index f28612140..6cdd6e817 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -62,7 +62,7 @@ where amount_received >= min_buy_amount, "Sell should return more than mininum buy amount." ); - T::Currency::withdraw(asset_out, &account, amount_received); // burn the received tokens + T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens Ok(MultiAsset::from((wanted.id, amount_received)).into()) }) .map_err(|_| give) From 4a7093002fdfeb9e73c85f1889a94bb75ac5e91a Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 14:43:45 +0200 Subject: [PATCH 10/73] implement buy for ExchangeAsset --- pallets/omnipool/src/tests/xcm_exchange.rs | 39 ++++++++++++- pallets/omnipool/src/xcm_exchange.rs | 66 +++++++++++++++------- 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index 009307ed9..90acaf543 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -46,7 +46,7 @@ impl Convert> for CurrencyIdConvert { } #[test] -fn omni_exchanger_exchanges_supported_assets() { +fn omni_exchanger_allows_selling_supported_assets() { // Arrange ExtBuilder::default() .with_endowed_accounts(vec![ @@ -73,3 +73,40 @@ fn omni_exchanger_exchanges_supported_assets() { assert_eq!(Balances::free_balance(&ExchangeTempAccount::get()), 0); }); } + +#[test] +fn omni_exchanger_allows_buying_supported_assets() { + // Arrange + ExtBuilder::default() + .with_endowed_accounts(vec![ + (Omnipool::protocol_account(), DAI, 1000 * ONE), + (Omnipool::protocol_account(), HDX, NATIVE_AMOUNT), + ]) + .with_initial_pool(FixedU128::from_float(0.5), FixedU128::from(1)) + .build() + .execute_with(|| { + let given_amount = 100 * UNITS; + let give_asset = MultiAsset::from((GeneralIndex(DAI.into()), given_amount)); + let give = give_asset.clone().into(); + let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees + let want_asset = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)); + let want = want_asset.clone().into(); + // Act + let received = + OmniExchanger::::exchange_asset(None, give, &want, BUY) + .expect("should return ok"); + // Assert + let mut iter = received.fungible_assets_iter(); + let asset_received = iter.next().expect("there should be at least one asset"); + let left_over = iter.next().expect("there should be at least some left_over asset_in"); + assert!(iter.next().is_none(), "there should only be two assets returned"); + let Fungible(left_over_amount) = left_over.fun else { panic!("should be fungible")}; + assert_eq!(left_over, (GeneralIndex(DAI.into()), left_over_amount).into()); + assert!(left_over_amount < given_amount); + assert_eq!(asset_received, want_asset); + let Fungible(received_amount) = asset_received.fun else { panic!("should be fungible")}; + assert!(received_amount == wanted_amount); + assert_eq!(Tokens::free_balance(DAI, &ExchangeTempAccount::get()), 0); + assert_eq!(Balances::free_balance(&ExchangeTempAccount::get()), 0); + }); +} diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index 6cdd6e817..a8790ced6 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -25,30 +25,33 @@ where use orml_utilities::with_transaction_result; use sp_runtime::traits::Convert; + let account = if origin.is_none() { + TempAccount::get() + } else { + // TODO: support origins other than None? + return Err(give); + }; + let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + if give.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset given + if want.len() != 1 { + return Err(give); + }; // TODO: we assume only one asset wanted + // TODO: log errors + let given = give + .fungible_assets_iter() + .next() + .expect("length of 1 checked above; qed"); + + let Some(asset_in) = CurrencyIdConvert::convert(given.clone()) else { return Err(give) }; + let Some(wanted) = want.get(0) else { return Err(give) }; + let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; + if maximal { // sell - let account = if origin.is_none() { - TempAccount::get() - } else { - return Err(give); - }; - let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way - if give.len() != 1 { - return Err(give); - }; // TODO: we assume only one asset given - if want.len() != 1 { - return Err(give); - }; // TODO: we assume only one asset wanted - let given = give - .fungible_assets_iter() - .next() - .expect("length of 1 checked above; qed"); - // TODO: log errors let Fungible(amount) = given.fun else { return Err(give) }; - let Some(asset_in) = CurrencyIdConvert::convert(given) else { return Err(give) }; - let Some(wanted) = want.get(0) else { return Err(give) }; let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; - let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; // TODO: unnecessary clone, maybe? with_transaction_result(|| { T::Currency::deposit(asset_in, &account, amount)?; // mint the incoming tokens @@ -68,7 +71,28 @@ where .map_err(|_| give) } else { // buy - Err(give) // TODO + let Fungible(amount) = wanted.fun else { return Err(give) }; + let Fungible(max_sell_amount) = given.fun else { return Err(give) }; + + with_transaction_result(|| { + T::Currency::deposit(asset_in, &account, max_sell_amount)?; // mint the incoming tokens + Pallet::::buy(origin, asset_out, asset_in, amount, max_sell_amount)?; + let mut assets = vec![]; + let left_over = T::Currency::free_balance(asset_in, &account); + if left_over > 0 { + T::Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens + assets.push(MultiAsset::from((given.id, left_over))); + } + let amount_received = T::Currency::free_balance(asset_out, &account); + debug_assert!( + amount_received == amount, + "Buy should return exactly the amount we specified." + ); + T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens + assets.push(MultiAsset::from((wanted.id, amount_received))); + Ok(assets.into()) + }) + .map_err(|_| give) } } } From 51be40a7e771c21a68c0621199b85b65de31ce29 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 14:48:03 +0200 Subject: [PATCH 11/73] formatting --- pallets/omnipool/src/xcm_exchange.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index a8790ced6..f148e54b7 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -32,13 +32,14 @@ where return Err(give); }; let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + + // TODO: log errors if give.len() != 1 { return Err(give); }; // TODO: we assume only one asset given if want.len() != 1 { return Err(give); }; // TODO: we assume only one asset wanted - // TODO: log errors let given = give .fungible_assets_iter() .next() From e7cfd40f92701d0a3f005fef3ae6f2fcbe971843 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 15:03:17 +0200 Subject: [PATCH 12/73] small fixes --- integration-tests/src/exchange_asset.rs | 16 ++++++++-------- pallets/omnipool/src/xcm_exchange.rs | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 81ff0d29d..63ba8669d 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -19,7 +19,7 @@ use xcm_emulator::TestExt; use frame_support::dispatch::GetDispatchInfo; -fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>(give: M, want: M) -> VersionedXcm { +fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>(give: MultiAsset, want: M) -> VersionedXcm { use polkadot_runtime::xcm_config::BaseXcmWeight; use sp_runtime::traits::ConstU32; use xcm_builder::FixedWeightBounds; @@ -31,8 +31,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32; - //let context = GlobalConsensus(NetworkId::Polkadot).into(); - let context2 = X2( + let context = X2( GlobalConsensus(NetworkId::Polkadot).into(), Parachain(ACALA_PARA_ID).into(), ); @@ -40,9 +39,9 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( .get(0) .expect("should have at least 1 asset") .clone() - .reanchored(&dest, context2) + .reanchored(&dest, context) .expect("should reanchor"); - // TODO: reanchor + let give = give.reanchored(&dest, context).expect("should reanchor give"); let give: MultiAssetFilter = Definite(give.into()); let want = want.into(); let weight_limit = { @@ -95,10 +94,11 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { TestNet::reset(); dbg!("before hydra 1"); + let aca = 1; Hydra::execute_with(|| { assert_ok!(hydradx_runtime::AssetRegistry::set_location( hydradx_runtime::RuntimeOrigin::root(), - 1, + aca, hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) )); }); @@ -139,7 +139,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { dbg!("before hydra 2"); Hydra::execute_with(|| { assert_eq!( - hydradx_runtime::Tokens::free_balance(1, &AccountId::from(BOB)), + hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), BOB_INITIAL_NATIVE_BALANCE + 50 * UNITS - fees ); assert_eq!( @@ -147,7 +147,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees ); assert_eq!( - hydradx_runtime::Tokens::free_balance(1, &hydradx_runtime::Treasury::account_id()), + hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), fees ); }); diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index f148e54b7..23076af11 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -23,7 +23,6 @@ where ) -> Result { use orml_traits::MultiCurrency; use orml_utilities::with_transaction_result; - use sp_runtime::traits::Convert; let account = if origin.is_none() { TempAccount::get() From 1cec0992c0566a6c8eda2827afb0910dcc0c9e40 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 15:07:08 +0200 Subject: [PATCH 13/73] remove unused imports --- integration-tests/src/exchange_asset.rs | 9 +-------- pallets/omnipool/src/tests/xcm_exchange.rs | 3 +-- pallets/omnipool/src/xcm_exchange.rs | 5 +---- runtime/hydradx/src/xcm.rs | 3 +-- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 63ba8669d..ad26c288e 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -1,27 +1,20 @@ #![cfg(test)] use crate::polkadot_test_net::*; -use cumulus_primitives_core::ParaId; use frame_support::weights::Weight; use frame_support::{ assert_ok, pallet_prelude::*, - sp_runtime::{FixedU128, Permill}, - traits::Contains, }; -use hex_literal::hex; use orml_traits::currency::MultiCurrency; -use polkadot_xcm::{latest::prelude::*, v3::WeightLimit, VersionedMultiAssets, VersionedXcm}; +use polkadot_xcm::{latest::prelude::*, VersionedXcm}; use pretty_assertions::assert_eq; -use sp_core::H256; -use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; use xcm_emulator::TestExt; use frame_support::dispatch::GetDispatchInfo; fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>(give: MultiAsset, want: M) -> VersionedXcm { use polkadot_runtime::xcm_config::BaseXcmWeight; - use sp_runtime::traits::ConstU32; use xcm_builder::FixedWeightBounds; use xcm_executor::traits::WeightBounds; diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index 90acaf543..f747ca288 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -2,11 +2,10 @@ use super::*; use crate::tests::mock::AssetId as CurrencyId; use crate::tests::mock::{Balances, Tokens}; use crate::xcm_exchange::OmniExchanger; -use frame_support::{assert_noop, parameter_types}; +use frame_support::parameter_types; use polkadot_xcm::latest::prelude::*; use pretty_assertions::assert_eq; use sp_runtime::traits::Convert; -use sp_runtime::Permill; use sp_runtime::SaturatedConversion; use xcm_executor::traits::AssetExchange; diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index 23076af11..8fa4556dc 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -1,12 +1,9 @@ use crate::{Config, Pallet}; -use frame_support::traits::OriginTrait; -use frame_system::pallet_prelude::OriginFor; use polkadot_xcm::latest::prelude::*; use sp_core::Get; -use sp_runtime::traits::{AccountIdConversion, Convert}; +use sp_runtime::traits::Convert; use std::marker::PhantomData; use xcm_executor::traits::AssetExchange; - pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); impl AssetExchange for OmniExchanger diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 53e3ec574..fdd11e384 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -3,7 +3,6 @@ use super::{AssetId, *}; use common_runtime::adapters::ReroutingMultiCurrencyAdapter; use cumulus_primitives_core::ParaId; use frame_support::{ - storage::with_transaction, traits::{Everything, Nothing}, PalletId, }; @@ -22,7 +21,7 @@ use xcm_builder::{ SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, }; -use xcm_executor::{traits::AssetExchange, Config, XcmExecutor}; +use xcm_executor::{Config, XcmExecutor}; pub type LocalOriginToLocation = SignedToAccountId32; From 18d586d501ecf868f1e65e068ec68360c007e48f Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 16:24:50 +0200 Subject: [PATCH 14/73] get ExchangeAsset integration test working --- integration-tests/src/exchange_asset.rs | 58 ++++++++++++++++------ integration-tests/src/polkadot_test_net.rs | 1 + pallets/omnipool/Cargo.toml | 8 ++- pallets/omnipool/src/xcm_exchange.rs | 4 +- runtime/hydradx/src/xcm.rs | 4 +- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index ad26c288e..e41ae755a 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -1,19 +1,21 @@ #![cfg(test)] use crate::polkadot_test_net::*; +use common_runtime::CORE_ASSET_ID; use frame_support::weights::Weight; -use frame_support::{ - assert_ok, - pallet_prelude::*, -}; +use frame_support::{assert_ok, pallet_prelude::*}; use orml_traits::currency::MultiCurrency; use polkadot_xcm::{latest::prelude::*, VersionedXcm}; use pretty_assertions::assert_eq; +use sp_runtime::{FixedU128, Permill}; use xcm_emulator::TestExt; use frame_support::dispatch::GetDispatchInfo; -fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>(give: MultiAsset, want: M) -> VersionedXcm { +fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( + give: MultiAsset, + want: M, +) -> VersionedXcm { use polkadot_runtime::xcm_config::BaseXcmWeight; use xcm_builder::FixedWeightBounds; use xcm_executor::traits::WeightBounds; @@ -23,7 +25,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let dest = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded - let max_assets = assets.len() as u32; + let max_assets = assets.len() as u32 + 1; let context = X2( GlobalConsensus(NetworkId::Polkadot).into(), Parachain(ACALA_PARA_ID).into(), @@ -87,13 +89,38 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { TestNet::reset(); dbg!("before hydra 1"); - let aca = 1; + let aca = 1234; + let mut price = None; Hydra::execute_with(|| { - assert_ok!(hydradx_runtime::AssetRegistry::set_location( + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"ACA".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(aca), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + init_omnipool(); + let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); + + let token_price = FixedU128::from_float(1.0); + assert_ok!(hydradx_runtime::Tokens::deposit(aca, &omnipool_account, 3000 * UNITS)); + + assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), aca, - hydradx_runtime::AssetLocation(MultiLocation::new(1, X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)))) + token_price, + Permill::from_percent(100), + AccountId::from(BOB), )); + use hydradx_traits::pools::SpotPriceProvider; + price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, aca); }); dbg!("after hydra 1"); @@ -102,7 +129,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { dbg!("execute acala"); let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( MultiAsset::from((GeneralIndex(0), 50 * UNITS)), - MultiAsset::from((Here, 300 * UNITS)), + MultiAsset::from((GeneralIndex(CORE_ASSET_ID.into()), 300 * UNITS)), ); //Act let res = hydradx_runtime::PolkadotXcm::execute( @@ -128,17 +155,16 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { }); dbg!("after acala"); - let fees = 400641025641; + let fees = 500801282051; dbg!("before hydra 2"); Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), - BOB_INITIAL_NATIVE_BALANCE + 50 * UNITS - fees - ); - assert_eq!( - hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), - BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS - fees + 50 * UNITS - fees ); + // We receive about 39_101 HDX + let received = 39_101 * UNITS + BOB_INITIAL_NATIVE_BALANCE + 207_131_554_396; + assert_eq!(hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), received); assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), fees diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index 3149b9c3f..141c6dc9a 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -239,6 +239,7 @@ pub fn hydra_ext() -> sp_io::TestExternalities { (LRNA, Price::from(1)), (DAI, Price::from(1)), (BTC, Price::from_inner(134_000_000)), + (1234, Price::from(1)), ], account_currencies: vec![], } diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index 8cc2cb151..7fd24189e 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.3.1", default-features = false, features = ["derive" codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } # primitives +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -43,7 +44,6 @@ log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } -sp-core = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } #Polkadot @@ -51,7 +51,6 @@ xcm-executor = { workspace = true } polkadot-xcm = { workspace = true } [dev-dependencies] -sp-core = { workspace = true } sp-io = { workspace = true } sp-tracing = { workspace = true } pallet-balances = { workspace = true } @@ -64,13 +63,13 @@ pretty_assertions = "1.2.1" default = ["std"] std = [ "codec/std", + "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", "frame-support/std", "frame-system/std", "scale-info/std", - "sp-core/std", - "sp-io/std", "pallet-balances/std", "orml-tokens/std", "frame-benchmarking/std", @@ -78,7 +77,6 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "sp-core", "sp-io", "pallet-balances", ] diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index 8fa4556dc..b6378a25d 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -2,7 +2,7 @@ use crate::{Config, Pallet}; use polkadot_xcm::latest::prelude::*; use sp_core::Get; use sp_runtime::traits::Convert; -use std::marker::PhantomData; +use sp_std::marker::PhantomData; use xcm_executor::traits::AssetExchange; pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); @@ -74,7 +74,7 @@ where with_transaction_result(|| { T::Currency::deposit(asset_in, &account, max_sell_amount)?; // mint the incoming tokens Pallet::::buy(origin, asset_out, asset_in, amount, max_sell_amount)?; - let mut assets = vec![]; + let mut assets = sp_std::vec::Vec::new(); let left_over = T::Currency::free_balance(asset_in, &account); if left_over > 0 { T::Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index fdd11e384..375b7a304 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -9,6 +9,7 @@ use frame_support::{ use hydradx_adapters::{MultiCurrencyTrader, ToFeeReceiver}; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; pub use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; +use pallet_omnipool::xcm_exchange::OmniExchanger; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use polkadot_xcm::v3::prelude::*; @@ -74,6 +75,7 @@ parameter_types! { pub const MaxAssetsForTransfer: usize = 2; pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); + pub TempAccount: AccountId = [42; 32].into(); } pub struct XcmConfig; @@ -106,7 +108,7 @@ impl Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetLocker = (); - type AssetExchanger = (); + type AssetExchanger = OmniExchanger; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; From 79dd5063f3d8dd8a0feaaffcc3fe0eb312e9bf38 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 16:28:58 +0200 Subject: [PATCH 15/73] remove unnecessary clone --- pallets/omnipool/src/tests/xcm_exchange.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index f747ca288..95cb32947 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -86,7 +86,7 @@ fn omni_exchanger_allows_buying_supported_assets() { .execute_with(|| { let given_amount = 100 * UNITS; let give_asset = MultiAsset::from((GeneralIndex(DAI.into()), given_amount)); - let give = give_asset.clone().into(); + let give = give_asset.into(); let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees let want_asset = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)); let want = want_asset.clone().into(); From 1c97f27a370d587128f5e7b91c811e50ba39d4ec Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 16:30:06 +0200 Subject: [PATCH 16/73] remove unnecessary into --- integration-tests/src/exchange_asset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index e41ae755a..3bfd8ef32 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -27,8 +27,8 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32 + 1; let context = X2( - GlobalConsensus(NetworkId::Polkadot).into(), - Parachain(ACALA_PARA_ID).into(), + GlobalConsensus(NetworkId::Polkadot), + Parachain(ACALA_PARA_ID), ); let fees = assets .get(0) From 0522bc324c03f5442aeafdf10eb317f9dfd7e837 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 20 Jun 2023 16:30:25 +0200 Subject: [PATCH 17/73] formatting --- integration-tests/src/exchange_asset.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 3bfd8ef32..c0ef8f4be 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -26,10 +26,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32 + 1; - let context = X2( - GlobalConsensus(NetworkId::Polkadot), - Parachain(ACALA_PARA_ID), - ); + let context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); let fees = assets .get(0) .expect("should have at least 1 asset") From 142731911828f446a5dc0072dc17501e76592c8f Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 26 Jun 2023 13:48:08 +0200 Subject: [PATCH 18/73] remove debug statement --- integration-tests/src/exchange_asset.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index c0ef8f4be..a2656ca11 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -85,7 +85,6 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { //Arrange TestNet::reset(); - dbg!("before hydra 1"); let aca = 1234; let mut price = None; Hydra::execute_with(|| { @@ -119,11 +118,8 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { use hydradx_traits::pools::SpotPriceProvider; price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, aca); }); - dbg!("after hydra 1"); - dbg!("before acala"); Acala::execute_with(|| { - dbg!("execute acala"); let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( MultiAsset::from((GeneralIndex(0), 50 * UNITS)), MultiAsset::from((GeneralIndex(CORE_ASSET_ID.into()), 300 * UNITS)), @@ -148,12 +144,9 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } )) )); - dbg!("end execute acala"); }); - dbg!("after acala"); let fees = 500801282051; - dbg!("before hydra 2"); Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), From 3224da002632923b38f510d29c4e00db84c464b4 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 26 Jun 2023 17:11:09 +0200 Subject: [PATCH 19/73] add and update comments --- integration-tests/src/exchange_asset.rs | 32 +++++++++++++++++++++++++ pallets/omnipool/src/xcm_exchange.rs | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index a2656ca11..26ab714ae 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -161,3 +161,35 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { ); }); } + +// TODO test with Acala -> Hydra Swap -> Acala + +// Support different transfers of swap results +// send HDX back to Acala +// DepositReserveAsset { assets: hdx_filter, dest: acala, xcm: +// Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { +// assets: Wild(AllCounted(max_assets)), +// beneficiary, +// }]) +// }, +// send ACA back to Acala +// InitiateReserveWithdraw { assets: aca_filter, reserve: acala, xcm: +// Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { +// assets: Wild(AllCounted(max_assets)), +// beneficiary, +// }]) +// }, +// send BTC back to Acala +// InitiateReserveWithdraw { assets: btc_filter, reserve: interlay, xcm: // Hydra +// Xcm(vec![BuyExecution { fees, weight_limit }, DepositReserveAsset { // Interlay +// assets: Wild(AllCounted(max_assets)), +// dest: acala, +// xcm: Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { // Acala +// assets: Wild(AllCounted(max_assets)), +// beneficiary, +// }]), +// }]) +// }, +// send HDX to interlay +// send BTC to interlay +// send ACA to interlay diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index b6378a25d..514c5ca5a 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -32,7 +32,7 @@ where // TODO: log errors if give.len() != 1 { return Err(give); - }; // TODO: we assume only one asset given + }; // TODO: support multiple input assets if want.len() != 1 { return Err(give); }; // TODO: we assume only one asset wanted From 7f58b87408631a597db9189f3063ff2e9b6ef745 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 5 Jul 2023 10:50:09 +0200 Subject: [PATCH 20/73] add negative test case for disallowing multiple assets --- pallets/omnipool/src/tests/xcm_exchange.rs | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/pallets/omnipool/src/tests/xcm_exchange.rs index 95cb32947..81c5eb735 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/pallets/omnipool/src/tests/xcm_exchange.rs @@ -2,7 +2,7 @@ use super::*; use crate::tests::mock::AssetId as CurrencyId; use crate::tests::mock::{Balances, Tokens}; use crate::xcm_exchange::OmniExchanger; -use frame_support::parameter_types; +use frame_support::{assert_noop, parameter_types}; use polkadot_xcm::latest::prelude::*; use pretty_assertions::assert_eq; use sp_runtime::traits::Convert; @@ -109,3 +109,33 @@ fn omni_exchanger_allows_buying_supported_assets() { assert_eq!(Balances::free_balance(&ExchangeTempAccount::get()), 0); }); } + +#[test] +fn omni_exchanger_should_not_allow_trading_for_multiple_assets() { + // Arrange + ExtBuilder::default() + .with_endowed_accounts(vec![ + (Omnipool::protocol_account(), DAI, 1000 * ONE), + (Omnipool::protocol_account(), HDX, NATIVE_AMOUNT), + ]) + .with_initial_pool(FixedU128::from_float(0.5), FixedU128::from(1)) + .build() + .execute_with(|| { + let give: MultiAssets = MultiAsset::from((GeneralIndex(DAI.into()), 100 * UNITS)).into(); + let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees + let want1: MultiAsset = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)); + let want2: MultiAsset = MultiAsset::from((GeneralIndex(DAI.into()), wanted_amount)); + let want: MultiAssets = vec![want1, want2].into(); + + // Act and assert + assert_noop!( + OmniExchanger::::exchange_asset( + None, + give.clone().into(), + &want, + SELL + ), + give + ); + }); +} From dc390b31bff7af4a55b916b8c96ed2dfcbe22d96 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 11 Jul 2023 15:31:42 +0200 Subject: [PATCH 21/73] add AllowTransferAndSwap --- runtime/hydradx/src/xcm.rs | 5 +- runtime/hydradx/src/xcm/filters.rs | 73 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 runtime/hydradx/src/xcm/filters.rs diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 375b7a304..55d113736 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -24,6 +24,9 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; +mod filters; +pub use filters::*; + pub type LocalOriginToLocation = SignedToAccountId32; pub type Barrier = ( @@ -187,7 +190,7 @@ impl pallet_xcm::Config for Runtime { type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; + type XcmExecuteFilter = AllowTransferAndSwap; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs new file mode 100644 index 000000000..9c612820d --- /dev/null +++ b/runtime/hydradx/src/xcm/filters.rs @@ -0,0 +1,73 @@ +use core::{cell::Cell, marker::PhantomData}; + +use frame_support::traits::Contains; +use polkadot_xcm::v3::prelude::*; +use sp_runtime::Either; + +pub struct AllowTransferAndSwap(PhantomData); + +fn allowed_or_recurse(inst: &Instruction) -> Either> { + match inst { + ClearOrigin | + ClaimAsset {..} | + ExchangeAsset { .. } | + WithdrawAsset (..) | + TransferAsset { .. } | + DepositAsset { .. } | + SetTopic(..) | ClearTopic | + ExpectAsset(..) | BurnAsset(..) | + BuyExecution { .. } => Either::Left(true), + InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => Either::Right(xcm), + _ => Either::Left(false), + } +} + +fn check_instructions_recursively(xcm: &Xcm, depth: u16) -> bool { + if depth > 6 { return false } // TODO: make configurable? + let limit = 10; // TODO: make configurable? + let count = Cell::new(0usize); + let mut iter = xcm.inner().iter(); + while let (true, Some(inst)) = (count.get() < limit, iter.next()) { + count.set(count.get() + 1); + match allowed_or_recurse(inst) { + Either::Left(true) => continue, + Either::Left(false) => return false, + Either::Right(xcm) => if check_instructions_recursively(xcm, depth + 1) { + continue + } else { + return false + } + } + } + true +} + +impl Contains<(MultiLocation, Xcm)> for AllowTransferAndSwap { + fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { + // allow root to execute XCM + if loc == &MultiLocation::here() { + return true; + } + check_instructions_recursively(xcm, 0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use codec::Encode; + use frame_support::pallet_prelude::Weight; + + #[test] + fn allow_transfer_and_swap_should_filter_transact() { + let call = crate::RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); + let xcm = Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: Weight::from_parts(1, 1), call: call.into() }]); + let loc = MultiLocation::new(0, AccountId32 { + network: None, + id: [0; 32].into() + }); + assert_eq!(AllowTransferAndSwap::::contains(&(loc, xcm)), + false); + } +} \ No newline at end of file From c372eaaaa344c444c412128f135675504085489e Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 11 Jul 2023 15:32:16 +0200 Subject: [PATCH 22/73] formatting --- runtime/hydradx/src/xcm/filters.rs | 60 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 9c612820d..a6e4cdd88 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -8,22 +8,28 @@ pub struct AllowTransferAndSwap(PhantomData); fn allowed_or_recurse(inst: &Instruction) -> Either> { match inst { - ClearOrigin | - ClaimAsset {..} | - ExchangeAsset { .. } | - WithdrawAsset (..) | - TransferAsset { .. } | - DepositAsset { .. } | - SetTopic(..) | ClearTopic | - ExpectAsset(..) | BurnAsset(..) | - BuyExecution { .. } => Either::Left(true), - InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => Either::Right(xcm), + ClearOrigin + | ClaimAsset { .. } + | ExchangeAsset { .. } + | WithdrawAsset(..) + | TransferAsset { .. } + | DepositAsset { .. } + | SetTopic(..) + | ClearTopic + | ExpectAsset(..) + | BurnAsset(..) + | BuyExecution { .. } => Either::Left(true), + InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { + Either::Right(xcm) + } _ => Either::Left(false), } } fn check_instructions_recursively(xcm: &Xcm, depth: u16) -> bool { - if depth > 6 { return false } // TODO: make configurable? + if depth > 6 { + return false; + } // TODO: make configurable? let limit = 10; // TODO: make configurable? let count = Cell::new(0usize); let mut iter = xcm.inner().iter(); @@ -32,10 +38,12 @@ fn check_instructions_recursively(xcm: &Xcm, depth: u1 match allowed_or_recurse(inst) { Either::Left(true) => continue, Either::Left(false) => return false, - Either::Right(xcm) => if check_instructions_recursively(xcm, depth + 1) { - continue - } else { - return false + Either::Right(xcm) => { + if check_instructions_recursively(xcm, depth + 1) { + continue; + } else { + return false; + } } } } @@ -62,12 +70,18 @@ mod tests { #[test] fn allow_transfer_and_swap_should_filter_transact() { let call = crate::RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); - let xcm = Xcm(vec![Transact { origin_kind: OriginKind::Native, require_weight_at_most: Weight::from_parts(1, 1), call: call.into() }]); - let loc = MultiLocation::new(0, AccountId32 { - network: None, - id: [0; 32].into() - }); - assert_eq!(AllowTransferAndSwap::::contains(&(loc, xcm)), - false); + let xcm = Xcm(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1, 1), + call: call.into(), + }]); + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [0; 32].into(), + }, + ); + assert_eq!(AllowTransferAndSwap::::contains(&(loc, xcm)), false); } -} \ No newline at end of file +} From 53753e9789c94d3e0b6b4733ce1b4f64d54c2b70 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 11 Jul 2023 15:34:16 +0200 Subject: [PATCH 23/73] make clippy happy --- runtime/hydradx/src/xcm/filters.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index a6e4cdd88..eb960fc43 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -79,9 +79,9 @@ mod tests { 0, AccountId32 { network: None, - id: [0; 32].into(), + id: [1; 32], }, ); - assert_eq!(AllowTransferAndSwap::::contains(&(loc, xcm)), false); + assert!(!AllowTransferAndSwap::::contains(&(loc, xcm))); } } From a95b961cff1cc889adafb7d76c529b3eee61d87e Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 11 Jul 2023 15:59:32 +0200 Subject: [PATCH 24/73] fix filter - add set fees to allow functionality of transfer and swap --- runtime/hydradx/src/xcm/filters.rs | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index eb960fc43..ecc55e2cd 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -18,6 +18,7 @@ fn allowed_or_recurse(inst: &Instruction) -> Either Either::Left(true), InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { Either::Right(xcm) @@ -84,4 +85,51 @@ mod tests { ); assert!(!AllowTransferAndSwap::::contains(&(loc, xcm))); } + + #[test] + fn allow_transfer_and_swap_should_allow_a_transfer_and_swap() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + ExchangeAsset { + give, + want, + maximal: true, + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]); + + let message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + TransferReserveAsset { assets, dest, xcm }, + ]); + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(AllowTransferAndSwap::::contains(&(loc, message))); + } } From 8d5403575bbeb36f89c69d01d1ba924b68af032c Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 11 Jul 2023 16:18:08 +0200 Subject: [PATCH 25/73] adjust recursion level and add test for it --- runtime/hydradx/src/xcm/filters.rs | 51 +++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index ecc55e2cd..ada6d2e5b 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -28,7 +28,7 @@ fn allowed_or_recurse(inst: &Instruction) -> Either(xcm: &Xcm, depth: u16) -> bool { - if depth > 6 { + if depth >= 6 { return false; } // TODO: make configurable? let limit = 10; // TODO: make configurable? @@ -132,4 +132,53 @@ mod tests { //Act and assert assert!(AllowTransferAndSwap::::contains(&(loc, message))); } + + #[test] + fn allow_transfer_and_swap_should_filter_too_deep_xcm() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit, + }]); + + for _ in 0..5 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm, + }]); + } + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + } } From 7de4096bee1790f3b6c03992d689394d6b011148 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 11 Jul 2023 18:14:47 +0200 Subject: [PATCH 26/73] add ignored test for filter too many instructions in depth --- runtime/hydradx/src/xcm/filters.rs | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index ada6d2e5b..10dea2674 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -181,4 +181,62 @@ mod tests { //Act and assert assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); } + + #[ignore] + #[test] + fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![ + TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit.clone(), + }; + 5 + ]); + + //TODO: continue from here + //TODO: remove limit per level and create global limit fe 100, so we can have two tests, + // one one 100 instructions on one level, other is with multiple levels, and both should be filtered out + for _ in 0..5 { + let xcm = message.clone(); + message = Xcm(vec![ + TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: xcm.clone(), + }; + 2 + ]); + } + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + } } From 12a046a6d4eaf09ca1eb5a37b9734fc7d71b2597 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 10:46:38 +0200 Subject: [PATCH 27/73] add positive case for not fitlering message with max deep --- runtime/hydradx/src/xcm/filters.rs | 103 +++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 10dea2674..543f2b09e 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -182,6 +182,55 @@ mod tests { assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); } + #[test] + fn allow_transfer_and_swap_should_not_filter_message_with_max_deep() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit, + }]); + + for _ in 0..4 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm, + }]); + } + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(AllowTransferAndSwap::<()>::contains(&(loc, message))); + } + #[ignore] #[test] fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions() { @@ -239,4 +288,58 @@ mod tests { //Act and assert assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); } + /* + #[test] + fn asd() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![ + TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit.clone(), + }; + 3 + ]); + + for _ in 0..5 { + let xcm = message.clone(); + message = Xcm(vec![ + TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: xcm.clone(), + }; + 2 + ]); + } + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + }*/ } From 02f87557cd7c9d4271f898729aa2000a3f1b5642 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 10:49:02 +0200 Subject: [PATCH 28/73] remove unused code --- runtime/hydradx/src/xcm/filters.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 543f2b09e..1896e45bb 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -137,9 +137,6 @@ mod tests { fn allow_transfer_and_swap_should_filter_too_deep_xcm() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); let assets: MultiAssets = fees.clone().into(); let max_assets = 2; @@ -186,9 +183,6 @@ mod tests { fn allow_transfer_and_swap_should_not_filter_message_with_max_deep() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); let assets: MultiAssets = fees.clone().into(); let max_assets = 2; @@ -236,9 +230,6 @@ mod tests { fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); let assets: MultiAssets = fees.clone().into(); let max_assets = 2; From 4e50282f4f14c109d281248eee08dded38a3bc0d Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 11:30:49 +0200 Subject: [PATCH 29/73] adjust ignored test to reach max instruction limit --- runtime/hydradx/src/xcm/filters.rs | 43 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 1896e45bb..2a06e868c 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -279,9 +279,10 @@ mod tests { //Act and assert assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); } - /* + + #[ignore] #[test] - fn asd() { + fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions2() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let weight_limit = WeightLimit::Unlimited; @@ -308,20 +309,36 @@ mod tests { dest, xcm: deposit.clone(), }; - 3 + 5 ]); - for _ in 0..5 { + for _ in 0..3 { let xcm = message.clone(); - message = Xcm(vec![ - TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: xcm.clone(), - }; - 2 - ]); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: xcm.clone(), + }]); } + + //It has 14 instruction + let mut instructions_with_max_deep: Vec> = + vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: message.clone(), + }]; + + let mut rest: Vec> = vec![ + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }; + 87 + ]; + + instructions_with_max_deep.append(&mut rest); + let loc = MultiLocation::new( 0, AccountId32 { @@ -332,5 +349,5 @@ mod tests { //Act and assert assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); - }*/ + } } From b1770a74b94ec6c47f7d97c8e722d1d15d4cb0eb Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 12:10:24 +0200 Subject: [PATCH 30/73] renaming --- runtime/hydradx/src/xcm/filters.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 2a06e868c..0e69353fb 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -31,11 +31,11 @@ fn check_instructions_recursively(xcm: &Xcm, depth: u1 if depth >= 6 { return false; } // TODO: make configurable? - let limit = 10; // TODO: make configurable? - let count = Cell::new(0usize); + let limit_per_level = 10; // TODO: make configurable? + let depth_count = Cell::new(0usize); let mut iter = xcm.inner().iter(); - while let (true, Some(inst)) = (count.get() < limit, iter.next()) { - count.set(count.get() + 1); + while let (true, Some(inst)) = (depth_count.get() < limit_per_level, iter.next()) { + depth_count.set(depth_count.get() + 1); match allowed_or_recurse(inst) { Either::Left(true) => continue, Either::Left(false) => return false, From 4b0f7801a42eef4eb394dac8ed6908debad53f6c Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 14:45:28 +0200 Subject: [PATCH 31/73] add counting of instructions --- runtime/hydradx/src/xcm/filters.rs | 133 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 64 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 0e69353fb..c17bde470 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -27,20 +27,33 @@ fn allowed_or_recurse(inst: &Instruction) -> Either(xcm: &Xcm, depth: u16) -> bool { +fn check_instructions_recursively(xcm: &Xcm, depth: u16, instructions: &Cell) -> bool { if depth >= 6 { return false; - } // TODO: make configurable? + } + + // TODO: make configurable? let limit_per_level = 10; // TODO: make configurable? + let max_instructions = 100usize; // TODO: make configurable? let depth_count = Cell::new(0usize); + let mut instructions_count = instructions; let mut iter = xcm.inner().iter(); - while let (true, Some(inst)) = (depth_count.get() < limit_per_level, iter.next()) { - depth_count.set(depth_count.get() + 1); + while let (true, Some(inst)) = ( + /*depth_count.get() < limit_per_level,*/ + instructions_count.get() <= max_instructions, + iter.next(), + ) { + /*depth_count.set(depth_count.get() + 1);*/ + instructions_count.set(instructions_count.get() + 1); + if instructions_count.get() > max_instructions { + return false; + } + match allowed_or_recurse(inst) { Either::Left(true) => continue, Either::Left(false) => return false, Either::Right(xcm) => { - if check_instructions_recursively(xcm, depth + 1) { + if check_instructions_recursively(xcm, depth + 1, instructions_count) { continue; } else { return false; @@ -57,7 +70,9 @@ impl Contains<(MultiLocation, Xcm)> for AllowTransferA if loc == &MultiLocation::here() { return true; } - check_instructions_recursively(xcm, 0) + + let instructions = Cell::new(0usize); + check_instructions_recursively(xcm, 0, &instructions) } } @@ -225,11 +240,13 @@ mod tests { assert!(AllowTransferAndSwap::<()>::contains(&(loc, message))); } - #[ignore] #[test] - fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions() { + fn allow_transfer_and_swap_should_filter_messages_with_one_more_instruction_than_allowed() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); let assets: MultiAssets = fees.clone().into(); let max_assets = 2; @@ -245,29 +262,45 @@ mod tests { beneficiary, }]); - let mut message = Xcm(vec![ - TransferReserveAsset { + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit.clone(), + }]); + + for _ in 0..2 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { assets: assets.clone(), dest, - xcm: deposit.clone(), + xcm: xcm.clone(), + }]); + } + + //It has 5 instruction + let mut instructions_with_inner_xcms: Vec> = + vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: message.clone(), + }]; + + let mut rest: Vec> = vec![ + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, }; - 5 - ]); + 95 + ]; + + instructions_with_inner_xcms.append(&mut rest); + + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: Xcm(instructions_with_inner_xcms.clone()), + }]); - //TODO: continue from here - //TODO: remove limit per level and create global limit fe 100, so we can have two tests, - // one one 100 instructions on one level, other is with multiple levels, and both should be filtered out - for _ in 0..5 { - let xcm = message.clone(); - message = Xcm(vec![ - TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: xcm.clone(), - }; - 2 - ]); - } let loc = MultiLocation::new( 0, AccountId32 { @@ -280,9 +313,8 @@ mod tests { assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); } - #[ignore] #[test] - fn allow_transfer_and_swap_should_filter_messages_with_too_many_instructions2() { + fn asd() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let weight_limit = WeightLimit::Unlimited; @@ -298,46 +330,19 @@ mod tests { .into(); let dest = MultiLocation::new(1, Parachain(2047)); - let deposit = Xcm(vec![DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }]); - - let mut message = Xcm(vec![ - TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: deposit.clone(), - }; - 5 - ]); - - for _ in 0..3 { - let xcm = message.clone(); - message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: xcm.clone(), - }]); - } - - //It has 14 instruction - let mut instructions_with_max_deep: Vec> = - vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: message.clone(), - }]; - - let mut rest: Vec> = vec![ + let deposit = Xcm(vec![ DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, }; - 87 - ]; + 100 + ]); - instructions_with_max_deep.append(&mut rest); + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit.clone(), + }]); let loc = MultiLocation::new( 0, From eb8f66344a8c6a7a1cdcdef6056f9db9c7ea71f6 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 14:46:40 +0200 Subject: [PATCH 32/73] remove limit per level logic as we check for the max number of all instructions --- runtime/hydradx/src/xcm/filters.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index c17bde470..0c42649ea 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -30,19 +30,12 @@ fn allowed_or_recurse(inst: &Instruction) -> Either(xcm: &Xcm, depth: u16, instructions: &Cell) -> bool { if depth >= 6 { return false; - } - - // TODO: make configurable? - let limit_per_level = 10; // TODO: make configurable? + } // TODO: make configurable? let max_instructions = 100usize; // TODO: make configurable? let depth_count = Cell::new(0usize); let mut instructions_count = instructions; let mut iter = xcm.inner().iter(); - while let (true, Some(inst)) = ( - /*depth_count.get() < limit_per_level,*/ - instructions_count.get() <= max_instructions, - iter.next(), - ) { + while let (true, Some(inst)) = (instructions_count.get() <= max_instructions, iter.next()) { /*depth_count.set(depth_count.get() + 1);*/ instructions_count.set(instructions_count.get() + 1); if instructions_count.get() > max_instructions { From 0a0a1037a48d531c1d425abacd95b05103cff32a Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 14:49:58 +0200 Subject: [PATCH 33/73] remove unnecessary check --- runtime/hydradx/src/xcm/filters.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 0c42649ea..3b3e2b205 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -32,11 +32,9 @@ fn check_instructions_recursively(xcm: &Xcm, depth: u1 return false; } // TODO: make configurable? let max_instructions = 100usize; // TODO: make configurable? - let depth_count = Cell::new(0usize); let mut instructions_count = instructions; let mut iter = xcm.inner().iter(); - while let (true, Some(inst)) = (instructions_count.get() <= max_instructions, iter.next()) { - /*depth_count.set(depth_count.get() + 1);*/ + while let Some(inst) = iter.next() { instructions_count.set(instructions_count.get() + 1); if instructions_count.get() > max_instructions { return false; From a4649a0665849a6919e42db4182147e6f0997f58 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 15:21:16 +0200 Subject: [PATCH 34/73] add config for max xcm depth --- runtime/hydradx/src/xcm.rs | 7 ++++- runtime/hydradx/src/xcm/filters.rs | 44 +++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 55d113736..13ba9776d 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -183,6 +183,11 @@ parameter_types! { pub ReachableDest: Option = Some(Parent.into()); } +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); + pub const MaxXcmDepth: u16 = 5; +} + impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -190,7 +195,7 @@ impl pallet_xcm::Config for Runtime { type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = AllowTransferAndSwap; + type XcmExecuteFilter = AllowTransferAndSwap; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 3b3e2b205..b1ec5d1e1 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -2,9 +2,12 @@ use core::{cell::Cell, marker::PhantomData}; use frame_support::traits::Contains; use polkadot_xcm::v3::prelude::*; +use sp_core::{ConstU16, Get}; use sp_runtime::Either; -pub struct AllowTransferAndSwap(PhantomData); +pub struct AllowTransferAndSwap(PhantomData<(MaxXcmDepth, RuntimeCall)>) +where + MaxXcmDepth: Get; fn allowed_or_recurse(inst: &Instruction) -> Either> { match inst { @@ -27,10 +30,17 @@ fn allowed_or_recurse(inst: &Instruction) -> Either(xcm: &Xcm, depth: u16, instructions: &Cell) -> bool { - if depth >= 6 { +fn check_instructions_recursively( + xcm: &Xcm, + depth: u16, + instructions: &Cell, +) -> bool +where + MaxXcmDepth: Get, +{ + if depth > MaxXcmDepth::get() { return false; - } // TODO: make configurable? + } let max_instructions = 100usize; // TODO: make configurable? let mut instructions_count = instructions; let mut iter = xcm.inner().iter(); @@ -44,7 +54,7 @@ fn check_instructions_recursively(xcm: &Xcm, depth: u1 Either::Left(true) => continue, Either::Left(false) => return false, Either::Right(xcm) => { - if check_instructions_recursively(xcm, depth + 1, instructions_count) { + if check_instructions_recursively::(xcm, depth + 1, instructions_count) { continue; } else { return false; @@ -55,7 +65,11 @@ fn check_instructions_recursively(xcm: &Xcm, depth: u1 true } -impl Contains<(MultiLocation, Xcm)> for AllowTransferAndSwap { +impl Contains<(MultiLocation, Xcm)> + for AllowTransferAndSwap +where + MaxXcmDepth: Get, +{ fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { // allow root to execute XCM if loc == &MultiLocation::here() { @@ -63,7 +77,7 @@ impl Contains<(MultiLocation, Xcm)> for AllowTransferA } let instructions = Cell::new(0usize); - check_instructions_recursively(xcm, 0, &instructions) + check_instructions_recursively::(xcm, 0, &instructions) } } @@ -89,7 +103,9 @@ mod tests { id: [1; 32], }, ); - assert!(!AllowTransferAndSwap::::contains(&(loc, xcm))); + assert!(!AllowTransferAndSwap::, crate::RuntimeCall>::contains(&( + loc, xcm + ))); } #[test] @@ -136,7 +152,9 @@ mod tests { ); //Act and assert - assert!(AllowTransferAndSwap::::contains(&(loc, message))); + assert!(AllowTransferAndSwap::, crate::RuntimeCall>::contains(&( + loc, message + ))); } #[test] @@ -182,7 +200,7 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); } #[test] @@ -228,7 +246,7 @@ mod tests { ); //Act and assert - assert!(AllowTransferAndSwap::<()>::contains(&(loc, message))); + assert!(AllowTransferAndSwap::, ()>::contains(&(loc, message))); } #[test] @@ -301,7 +319,7 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); } #[test] @@ -344,6 +362,6 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::<()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); } } From d81f737fb1b91e46166fd804bd8835d5d21fe06d Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 15:31:26 +0200 Subject: [PATCH 35/73] make max instructions as config --- runtime/hydradx/src/xcm.rs | 3 +- runtime/hydradx/src/xcm/filters.rs | 54 ++++++++++++++++++------------ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 13ba9776d..15a81a188 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -186,6 +186,7 @@ parameter_types! { parameter_types! { pub ReachableDest: Option = Some(Parent.into()); pub const MaxXcmDepth: u16 = 5; + pub const MaxNumberOfInstructions: u16 = 100; } impl pallet_xcm::Config for Runtime { @@ -195,7 +196,7 @@ impl pallet_xcm::Config for Runtime { type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = AllowTransferAndSwap; + type XcmExecuteFilter = AllowTransferAndSwap; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index b1ec5d1e1..f78f7bbaa 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -5,9 +5,12 @@ use polkadot_xcm::v3::prelude::*; use sp_core::{ConstU16, Get}; use sp_runtime::Either; -pub struct AllowTransferAndSwap(PhantomData<(MaxXcmDepth, RuntimeCall)>) +pub struct AllowTransferAndSwap( + PhantomData<(MaxXcmDepth, MaxInstructions, RuntimeCall)>, +) where - MaxXcmDepth: Get; + MaxXcmDepth: Get, + MaxInstructions: Get; fn allowed_or_recurse(inst: &Instruction) -> Either> { match inst { @@ -30,23 +33,23 @@ fn allowed_or_recurse(inst: &Instruction) -> Either( +fn check_instructions_recursively( xcm: &Xcm, depth: u16, - instructions: &Cell, + instructions: &Cell, ) -> bool where MaxXcmDepth: Get, + MaxInstructions: Get, { if depth > MaxXcmDepth::get() { return false; } - let max_instructions = 100usize; // TODO: make configurable? let mut instructions_count = instructions; let mut iter = xcm.inner().iter(); while let Some(inst) = iter.next() { instructions_count.set(instructions_count.get() + 1); - if instructions_count.get() > max_instructions { + if instructions_count.get() > MaxInstructions::get() { return false; } @@ -54,7 +57,11 @@ where Either::Left(true) => continue, Either::Left(false) => return false, Either::Right(xcm) => { - if check_instructions_recursively::(xcm, depth + 1, instructions_count) { + if check_instructions_recursively::( + xcm, + depth + 1, + instructions_count, + ) { continue; } else { return false; @@ -65,10 +72,11 @@ where true } -impl Contains<(MultiLocation, Xcm)> - for AllowTransferAndSwap +impl Contains<(MultiLocation, Xcm)> + for AllowTransferAndSwap where MaxXcmDepth: Get, + MaxInstructions: Get, { fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { // allow root to execute XCM @@ -76,8 +84,8 @@ where return true; } - let instructions = Cell::new(0usize); - check_instructions_recursively::(xcm, 0, &instructions) + let instructions_count = Cell::new(0u16); + check_instructions_recursively::(xcm, 0, &instructions_count) } } @@ -103,9 +111,7 @@ mod tests { id: [1; 32], }, ); - assert!(!AllowTransferAndSwap::, crate::RuntimeCall>::contains(&( - loc, xcm - ))); + assert!(!AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(&(loc, xcm))); } #[test] @@ -152,9 +158,7 @@ mod tests { ); //Act and assert - assert!(AllowTransferAndSwap::, crate::RuntimeCall>::contains(&( - loc, message - ))); + assert!(AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(&(loc, message))); } #[test] @@ -200,7 +204,9 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( + loc, message + ))); } #[test] @@ -246,7 +252,9 @@ mod tests { ); //Act and assert - assert!(AllowTransferAndSwap::, ()>::contains(&(loc, message))); + assert!(AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( + loc, message + ))); } #[test] @@ -319,7 +327,9 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( + loc, message + ))); } #[test] @@ -362,6 +372,8 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ()>::contains(&(loc, message))); + assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( + loc, message + ))); } } From a54d45cf00655a95aa9759ccefcb8273b9641345 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 15:49:34 +0200 Subject: [PATCH 36/73] improve test assertions logic --- runtime/hydradx/src/xcm/filters.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index f78f7bbaa..b10d72655 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -111,7 +111,7 @@ mod tests { id: [1; 32], }, ); - assert!(!AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(&(loc, xcm))); + assert!(XcmExecuteFilterDoesNotAllow(&(loc, xcm))); } #[test] @@ -158,7 +158,7 @@ mod tests { ); //Act and assert - assert!(AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(&(loc, message))); + assert!(XcmExecuteFilterAllows(&(loc, message))); } #[test] @@ -204,9 +204,7 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( - loc, message - ))); + assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); } #[test] @@ -327,9 +325,7 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( - loc, message - ))); + assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); } #[test] @@ -372,8 +368,14 @@ mod tests { ); //Act and assert - assert!(!AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( - loc, message - ))); + assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); + } + + fn XcmExecuteFilterAllows(loc_and_message: &(MultiLocation, Xcm)) -> bool { + AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(loc_and_message) + } + + fn XcmExecuteFilterDoesNotAllow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool { + !AllowTransferAndSwap::, ConstU16<100>, ()>::contains(loc_and_message) } } From 9572d5406ca40fd975bcffb1aa51577464726065 Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 15:53:46 +0200 Subject: [PATCH 37/73] adjust test names --- runtime/hydradx/src/xcm/filters.rs | 40 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index b10d72655..5e942876c 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -96,8 +96,9 @@ mod tests { use codec::Encode; use frame_support::pallet_prelude::Weight; + //TODO: add more #[test] - fn allow_transfer_and_swap_should_filter_transact() { + fn xcm_execute_filter_should_not_allow_transact() { let call = crate::RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); let xcm = Xcm(vec![Transact { origin_kind: OriginKind::Native, @@ -111,11 +112,11 @@ mod tests { id: [1; 32], }, ); - assert!(XcmExecuteFilterDoesNotAllow(&(loc, xcm))); + assert!(xcm_execute_filter_does_not_allow(&(loc, xcm))); } #[test] - fn allow_transfer_and_swap_should_allow_a_transfer_and_swap() { + fn xcm_execute_filter_should_allow_a_transfer_and_swap() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let weight_limit = WeightLimit::Unlimited; @@ -158,11 +159,11 @@ mod tests { ); //Act and assert - assert!(XcmExecuteFilterAllows(&(loc, message))); + assert!(xcm_execute_filter_allows(&(loc, message))); } #[test] - fn allow_transfer_and_swap_should_filter_too_deep_xcm() { + fn xcm_execute_filter_should_filter_too_deep_xcm() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let assets: MultiAssets = fees.clone().into(); @@ -204,11 +205,11 @@ mod tests { ); //Act and assert - assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); + assert!(xcm_execute_filter_does_not_allow(&(loc, message))); } #[test] - fn allow_transfer_and_swap_should_not_filter_message_with_max_deep() { + fn xcm_execute_filter_should_not_filter_message_with_max_deep() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let assets: MultiAssets = fees.clone().into(); @@ -256,7 +257,7 @@ mod tests { } #[test] - fn allow_transfer_and_swap_should_filter_messages_with_one_more_instruction_than_allowed() { + fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let weight_limit = WeightLimit::Unlimited; @@ -325,11 +326,11 @@ mod tests { ); //Act and assert - assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); + assert!(xcm_execute_filter_does_not_allow(&(loc, message))); } #[test] - fn asd() { + fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_one_level() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); let weight_limit = WeightLimit::Unlimited; @@ -345,20 +346,14 @@ mod tests { .into(); let dest = MultiLocation::new(1, Parachain(2047)); - let deposit = Xcm(vec![ + let message_with_more_instructions_than_allowed = Xcm(vec![ DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary, }; - 100 + 101 ]); - let mut message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: deposit.clone(), - }]); - let loc = MultiLocation::new( 0, AccountId32 { @@ -368,14 +363,17 @@ mod tests { ); //Act and assert - assert!(XcmExecuteFilterDoesNotAllow(&(loc, message))); + assert!(xcm_execute_filter_does_not_allow(&( + loc, + message_with_more_instructions_than_allowed + ))); } - fn XcmExecuteFilterAllows(loc_and_message: &(MultiLocation, Xcm)) -> bool { + fn xcm_execute_filter_allows(loc_and_message: &(MultiLocation, Xcm)) -> bool { AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(loc_and_message) } - fn XcmExecuteFilterDoesNotAllow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool { + fn xcm_execute_filter_does_not_allow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool { !AllowTransferAndSwap::, ConstU16<100>, ()>::contains(loc_and_message) } } From 3f7aed0a063e7460a218f9cb47334397b2b8123d Mon Sep 17 00:00:00 2001 From: dmoka Date: Wed, 12 Jul 2023 16:24:03 +0200 Subject: [PATCH 38/73] adjust todo --- runtime/hydradx/src/xcm/filters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 5e942876c..3d5b9b0a0 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -96,7 +96,7 @@ mod tests { use codec::Encode; use frame_support::pallet_prelude::Weight; - //TODO: add more + //TODO: consider what others needs to be filtered out then add them to this test #[test] fn xcm_execute_filter_should_not_allow_transact() { let call = crate::RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); From fb1f26918e17a25fa42d34edfca0fb7a0a1a87b8 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 14:54:59 +0200 Subject: [PATCH 39/73] replace omnipool asset exchange logic to hydra and also adding todos for leftover works --- integration-tests/src/exchange_asset.rs | 2 + pallets/omnipool/src/xcm_exchange.rs | 2 + runtime/hydradx/src/xcm.rs | 4 +- runtime/hydradx/src/xcm/filters.rs | 4 +- runtime/hydradx/src/xcm/xcm_exchange.rs | 100 ++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 runtime/hydradx/src/xcm/xcm_exchange.rs diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 26ab714ae..b3804fc47 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -163,6 +163,8 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { } // TODO test with Acala -> Hydra Swap -> Acala +// TODO: we want to make sure that the different combinations work +// TODO: implement the most complex version: 4hops, 5 chains involved // Support different transfers of swap results // send HDX back to Acala diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs index 514c5ca5a..2002a9b23 100644 --- a/pallets/omnipool/src/xcm_exchange.rs +++ b/pallets/omnipool/src/xcm_exchange.rs @@ -6,6 +6,8 @@ use sp_std::marker::PhantomData; use xcm_executor::traits::AssetExchange; pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); +//TODO: remove this as it is replaced to hydradx runtime + impl AssetExchange for OmniExchanger where T: Config, diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 15a81a188..8437438c4 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -9,7 +9,6 @@ use frame_support::{ use hydradx_adapters::{MultiCurrencyTrader, ToFeeReceiver}; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; pub use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; -use pallet_omnipool::xcm_exchange::OmniExchanger; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use polkadot_xcm::v3::prelude::*; @@ -25,6 +24,8 @@ use xcm_builder::{ use xcm_executor::{Config, XcmExecutor}; mod filters; +mod xcm_exchange; + pub use filters::*; pub type LocalOriginToLocation = SignedToAccountId32; @@ -215,6 +216,7 @@ impl pallet_xcm::Config for Runtime { } pub struct CurrencyIdConvert; +use crate::xcm::xcm_exchange::OmniExchanger; use primitives::constants::chain::CORE_ASSET_ID; impl Convert> for CurrencyIdConvert { diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 3d5b9b0a0..4415e5016 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -36,7 +36,7 @@ fn allowed_or_recurse(inst: &Instruction) -> Either( xcm: &Xcm, depth: u16, - instructions: &Cell, + instructions: &Cell, //TODO: don't use std, use core or sp_std for Cell ) -> bool where MaxXcmDepth: Get, @@ -45,7 +45,7 @@ where if depth > MaxXcmDepth::get() { return false; } - let mut instructions_count = instructions; + let mut instructions_count = instructions; //TODO: use just a let mut u16 let mut iter = xcm.inner().iter(); while let Some(inst) = iter.next() { instructions_count.set(instructions_count.get() + 1); diff --git a/runtime/hydradx/src/xcm/xcm_exchange.rs b/runtime/hydradx/src/xcm/xcm_exchange.rs new file mode 100644 index 000000000..3bafa0b35 --- /dev/null +++ b/runtime/hydradx/src/xcm/xcm_exchange.rs @@ -0,0 +1,100 @@ +use common_runtime::AccountId; +use polkadot_xcm::latest::prelude::*; +use sp_core::Get; +use sp_runtime::traits::Convert; +use sp_std::marker::PhantomData; +use xcm_executor::traits::AssetExchange; + +//TODO: copy unit tests from omnipool and adapt them + +pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); + +impl AssetExchange for OmniExchanger +where + T: pallet_omnipool::Config, + TempAccount: Get, + CurrencyIdConvert: Convert>, +{ + fn exchange_asset( + origin: Option<&MultiLocation>, + give: xcm_executor::Assets, + want: &MultiAssets, + maximal: bool, + ) -> Result { + use orml_traits::MultiCurrency; + use orml_utilities::with_transaction_result; + + let account = if origin.is_none() { + TempAccount::get() + } else { + // TODO: we want to use temo account alwas becuase there is no sense using specific account for this "accounting/burning/minting/etc" temp work + return Err(give); + }; + let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + + // TODO: log errors - investigate using defernsive or use log::warn "xcm::exchange-asset" + if give.len() != 1 { + return Err(give); + }; // TODO: create an issue for this as it is easy to have multiple ExchangeAsset, and this would be just then an improvement + + //We assume only one asset wanted as translating into buy and sell is ambigous for multiple want assets + if want.len() != 1 { + return Err(give); + }; + let given = give + .fungible_assets_iter() + .next() + .expect("length of 1 checked above; qed"); // TODO: Use let Some(give), else Err, and also log + + let Some(asset_in) = CurrencyIdConvert::convert(given.clone()) else { return Err(give) }; + let Some(wanted) = want.get(0) else { return Err(give) }; + let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; + + if maximal { + // sell + let Fungible(amount) = given.fun else { return Err(give) }; + let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; + + with_transaction_result(|| { + T::Currency::deposit(asset_in, &account, amount)?; // mint the incoming tokens + pallet_omnipool::Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount)?; + debug_assert!( + T::Currency::free_balance(asset_in, &account) == 0, + "Sell should not leave any of the incoming asset." + ); + let amount_received = T::Currency::free_balance(asset_out, &account); + debug_assert!( + amount_received >= min_buy_amount, + "Sell should return more than mininum buy amount." + ); + T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens + Ok(MultiAsset::from((wanted.id, amount_received)).into()) + }) + .map_err(|_| give) + } else { + // buy + let Fungible(amount) = wanted.fun else { return Err(give) }; + let Fungible(max_sell_amount) = given.fun else { return Err(give) }; + + with_transaction_result(|| { + T::Currency::deposit(asset_in, &account, max_sell_amount)?; // mint the incoming tokens + pallet_omnipool::Pallet::::buy(origin, asset_out, asset_in, amount, max_sell_amount)?; + let mut assets = sp_std::vec::Vec::new(); + let left_over = T::Currency::free_balance(asset_in, &account); + if left_over > 0 { + T::Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens + assets.push(MultiAsset::from((given.id, left_over))); + } + let amount_received = T::Currency::free_balance(asset_out, &account); + debug_assert!( + amount_received == amount, + "Buy should return exactly the amount we specified." + ); + T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens + assets.push(MultiAsset::from((wanted.id, amount_received))); + Ok(assets.into()) + }) + .map_err(|_| give) + } + } +} From b1246f36086120cf6da4f9784a9e73ab34a2bceb Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 15:02:36 +0200 Subject: [PATCH 40/73] use proper error handling to prevent panicking --- runtime/hydradx/src/xcm/xcm_exchange.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/runtime/hydradx/src/xcm/xcm_exchange.rs b/runtime/hydradx/src/xcm/xcm_exchange.rs index 3bafa0b35..f9f90556c 100644 --- a/runtime/hydradx/src/xcm/xcm_exchange.rs +++ b/runtime/hydradx/src/xcm/xcm_exchange.rs @@ -41,10 +41,9 @@ where if want.len() != 1 { return Err(give); }; - let given = give - .fungible_assets_iter() - .next() - .expect("length of 1 checked above; qed"); // TODO: Use let Some(give), else Err, and also log + let Some(given) = give.fungible_assets_iter().next() else { + return Err(give); + }; let Some(asset_in) = CurrencyIdConvert::convert(given.clone()) else { return Err(give) }; let Some(wanted) = want.get(0) else { return Err(give) }; From 5d4f1d45596d7816e8cc12c168dcfd24288e8b90 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 19:20:54 +0200 Subject: [PATCH 41/73] move xcm exchange to hydra and and use router interface to decouple it from omnipool --- Cargo.lock | 28 +- integration-tests/src/exchange_asset.rs | 96 ++- pallets/omnipool/src/lib.rs | 1 - pallets/omnipool/src/tests/mod.rs | 1 - pallets/omnipool/src/xcm_exchange.rs | 97 --- runtime/hydradx/Cargo.toml | 6 + runtime/hydradx/src/xcm.rs | 5 +- runtime/hydradx/src/xcm/filters.rs | 12 +- runtime/hydradx/src/xcm/tests/mock.rs | 652 ++++++++++++++++++ runtime/hydradx/src/xcm/tests/mod.rs | 2 + .../hydradx/src/xcm}/tests/xcm_exchange.rs | 29 +- runtime/hydradx/src/xcm/xcm_exchange.rs | 75 +- 12 files changed, 831 insertions(+), 173 deletions(-) delete mode 100644 pallets/omnipool/src/xcm_exchange.rs create mode 100644 runtime/hydradx/src/xcm/tests/mock.rs create mode 100644 runtime/hydradx/src/xcm/tests/mod.rs rename {pallets/omnipool/src => runtime/hydradx/src/xcm}/tests/xcm_exchange.rs (86%) diff --git a/Cargo.lock b/Cargo.lock index b24d7e46f..2e0ddac12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1588,16 +1588,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ctr" version = "0.8.0" @@ -3857,6 +3847,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal 0.3.4", + "hydra-dx-math", "hydradx-adapters", "hydradx-traits", "orml-benchmarking", @@ -3909,6 +3900,8 @@ dependencies = [ "parachain-info", "parity-scale-codec", "polkadot-parachain", + "pretty_assertions", + "primitive-types", "primitives", "scale-info", "serde", @@ -6177,15 +6170,6 @@ version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "p256" version = "0.11.1" @@ -9340,13 +9324,11 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index b3804fc47..5c367b3a1 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -12,9 +12,13 @@ use xcm_emulator::TestExt; use frame_support::dispatch::GetDispatchInfo; +pub const SELL: bool = true; +pub const BUY: bool = false; + fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( give: MultiAsset, want: M, + is_sell: bool, ) -> VersionedXcm { use polkadot_runtime::xcm_config::BaseXcmWeight; use xcm_builder::FixedWeightBounds; @@ -48,7 +52,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( ExchangeAsset { give: give.clone(), want: want.clone(), - maximal: true, + maximal: is_sell, }, DepositAsset { assets: Wild(AllCounted(max_assets)), @@ -65,7 +69,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( ExchangeAsset { give, want, - maximal: true, + maximal: is_sell, }, DepositAsset { assets: Wild(AllCounted(max_assets)), @@ -81,7 +85,7 @@ fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( } #[test] -fn hydra_should_swap_assets_when_receiving_from_acala() { +fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { //Arrange TestNet::reset(); @@ -123,6 +127,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( MultiAsset::from((GeneralIndex(0), 50 * UNITS)), MultiAsset::from((GeneralIndex(CORE_ASSET_ID.into()), 300 * UNITS)), + SELL, ); //Act let res = hydradx_runtime::PolkadotXcm::execute( @@ -162,6 +167,91 @@ fn hydra_should_swap_assets_when_receiving_from_acala() { }); } +//TODO: double check if this buy make sense, especially in the end, bob's aca balanced changed more than the fee +#[test] +fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { + //Arrange + TestNet::reset(); + + let aca = 1234; + let mut price = None; + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"ACA".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(aca), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + init_omnipool(); + let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); + + let token_price = FixedU128::from_float(1.0); + assert_ok!(hydradx_runtime::Tokens::deposit(aca, &omnipool_account, 3000 * UNITS)); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + aca, + token_price, + Permill::from_percent(100), + AccountId::from(BOB), + )); + use hydradx_traits::pools::SpotPriceProvider; + price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, aca); + }); + + Acala::execute_with(|| { + let xcm = craft_exchange_asset_xcm::<_, hydradx_runtime::RuntimeCall>( + MultiAsset::from((GeneralIndex(0), 50 * UNITS)), + MultiAsset::from((GeneralIndex(CORE_ASSET_ID.into()), 300 * UNITS)), + BUY, + ); + //Act + let res = hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_ref_time(399_600_000_000), + ); + assert_ok!(res); + + //Assert + assert_eq!( + hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), + ALICE_INITIAL_NATIVE_BALANCE_ON_OTHER_PARACHAIN - 100 * UNITS + ); + // TODO: add utility macro? + assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + )); + }); + + let fees = 862495197993; + Hydra::execute_with(|| { + assert_eq!( + hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), + 100 * UNITS - fees + ); + assert_eq!( + hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), + BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS + ); + assert_eq!( + hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), + 500801282051 + ); + }); +} + // TODO test with Acala -> Hydra Swap -> Acala // TODO: we want to make sure that the different combinations work // TODO: implement the most complex version: 4hops, 5 chains involved diff --git a/pallets/omnipool/src/lib.rs b/pallets/omnipool/src/lib.rs index 5e702caab..b74e9324e 100644 --- a/pallets/omnipool/src/lib.rs +++ b/pallets/omnipool/src/lib.rs @@ -95,7 +95,6 @@ pub mod router_execution; pub mod traits; pub mod types; pub mod weights; -pub mod xcm_exchange; use crate::traits::{AssetInfo, OmnipoolHooks}; use crate::types::{AssetReserveState, AssetState, Balance, Position, SimpleImbalance, Tradability}; diff --git a/pallets/omnipool/src/tests/mod.rs b/pallets/omnipool/src/tests/mod.rs index 55cf6ebfb..9f070ed26 100644 --- a/pallets/omnipool/src/tests/mod.rs +++ b/pallets/omnipool/src/tests/mod.rs @@ -20,7 +20,6 @@ mod tradability; mod tvl; mod types; mod verification; -mod xcm_exchange; use mock::*; diff --git a/pallets/omnipool/src/xcm_exchange.rs b/pallets/omnipool/src/xcm_exchange.rs deleted file mode 100644 index 2002a9b23..000000000 --- a/pallets/omnipool/src/xcm_exchange.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::{Config, Pallet}; -use polkadot_xcm::latest::prelude::*; -use sp_core::Get; -use sp_runtime::traits::Convert; -use sp_std::marker::PhantomData; -use xcm_executor::traits::AssetExchange; -pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); - -//TODO: remove this as it is replaced to hydradx runtime - -impl AssetExchange for OmniExchanger -where - T: Config, - TempAccount: Get, - CurrencyIdConvert: Convert>, -{ - fn exchange_asset( - origin: Option<&MultiLocation>, - give: xcm_executor::Assets, - want: &MultiAssets, - maximal: bool, - ) -> Result { - use orml_traits::MultiCurrency; - use orml_utilities::with_transaction_result; - - let account = if origin.is_none() { - TempAccount::get() - } else { - // TODO: support origins other than None? - return Err(give); - }; - let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way - - // TODO: log errors - if give.len() != 1 { - return Err(give); - }; // TODO: support multiple input assets - if want.len() != 1 { - return Err(give); - }; // TODO: we assume only one asset wanted - let given = give - .fungible_assets_iter() - .next() - .expect("length of 1 checked above; qed"); - - let Some(asset_in) = CurrencyIdConvert::convert(given.clone()) else { return Err(give) }; - let Some(wanted) = want.get(0) else { return Err(give) }; - let Some(asset_out) = CurrencyIdConvert::convert(wanted.clone()) else { return Err(give) }; - - if maximal { - // sell - let Fungible(amount) = given.fun else { return Err(give) }; - let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; - - with_transaction_result(|| { - T::Currency::deposit(asset_in, &account, amount)?; // mint the incoming tokens - Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount)?; - debug_assert!( - T::Currency::free_balance(asset_in, &account) == 0, - "Sell should not leave any of the incoming asset." - ); - let amount_received = T::Currency::free_balance(asset_out, &account); - debug_assert!( - amount_received >= min_buy_amount, - "Sell should return more than mininum buy amount." - ); - T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens - Ok(MultiAsset::from((wanted.id, amount_received)).into()) - }) - .map_err(|_| give) - } else { - // buy - let Fungible(amount) = wanted.fun else { return Err(give) }; - let Fungible(max_sell_amount) = given.fun else { return Err(give) }; - - with_transaction_result(|| { - T::Currency::deposit(asset_in, &account, max_sell_amount)?; // mint the incoming tokens - Pallet::::buy(origin, asset_out, asset_in, amount, max_sell_amount)?; - let mut assets = sp_std::vec::Vec::new(); - let left_over = T::Currency::free_balance(asset_in, &account); - if left_over > 0 { - T::Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens - assets.push(MultiAsset::from((given.id, left_over))); - } - let amount_received = T::Currency::free_balance(asset_out, &account); - debug_assert!( - amount_received == amount, - "Buy should return exactly the amount we specified." - ); - T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens - assets.push(MultiAsset::from((wanted.id, amount_received))); - Ok(assets.into()) - }) - .map_err(|_| give) - } - } -} diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index d8821a8ba..b13a580a5 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -19,6 +19,7 @@ serde = { features = ["derive"], optional = true, version = "1.0.136" } codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false, features = ["derive"] } scale-info = { version = "2.3.1", default-features = false, features = ["derive"] } smallvec = "1.9.0" +primitive-types = {default-features = false, version = '0.12.0'} # local dependencies primitives = { workspace = true } @@ -65,6 +66,8 @@ warehouse-liquidity-mining = { workspace = true } pallet-otc = { workspace = true } pallet-route-executor = { workspace = true } +hydra-dx-math = { workspace = true } + # ORML dependencies orml-tokens = { workspace = true } orml-traits = { workspace = true } @@ -124,6 +127,9 @@ sp-version = { workspace = true } sp-trie = { workspace = true } sp-io = { workspace = true } +[dev-dependencies] +pretty_assertions = "1.4.0" + [features] default = ["std"] runtime-benchmarks = [ diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 8437438c4..db9599fe8 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -26,6 +26,9 @@ use xcm_executor::{Config, XcmExecutor}; mod filters; mod xcm_exchange; +#[cfg(test)] +mod tests; + pub use filters::*; pub type LocalOriginToLocation = SignedToAccountId32; @@ -112,7 +115,7 @@ impl Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetLocker = (); - type AssetExchanger = OmniExchanger; + type AssetExchanger = OmniExchanger; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs index 4415e5016..675e37dc0 100644 --- a/runtime/hydradx/src/xcm/filters.rs +++ b/runtime/hydradx/src/xcm/filters.rs @@ -45,7 +45,7 @@ where if depth > MaxXcmDepth::get() { return false; } - let mut instructions_count = instructions; //TODO: use just a let mut u16 + let instructions_count = instructions; //TODO: use just a let mut u16 let mut iter = xcm.inner().iter(); while let Some(inst) = iter.next() { instructions_count.set(instructions_count.get() + 1); @@ -260,9 +260,6 @@ mod tests { fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); let assets: MultiAssets = fees.clone().into(); let max_assets = 2; @@ -332,19 +329,12 @@ mod tests { #[test] fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_one_level() { //Arrange - let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); - let assets: MultiAssets = fees.clone().into(); - let max_assets = 2; let beneficiary = Junction::AccountId32 { id: [3; 32], network: None, } .into(); - let dest = MultiLocation::new(1, Parachain(2047)); let message_with_more_instructions_than_allowed = Xcm(vec![ DepositAsset { diff --git a/runtime/hydradx/src/xcm/tests/mock.rs b/runtime/hydradx/src/xcm/tests/mock.rs new file mode 100644 index 000000000..204164547 --- /dev/null +++ b/runtime/hydradx/src/xcm/tests/mock.rs @@ -0,0 +1,652 @@ +// This file is part of HydraDX. + +// Copyright (C) 2020-2022 Intergalactic, Limited (GIB). +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for Assets pallet. + +use crate::Amount; +use crate::MaxNumberOfTrades; +use crate::UNITS; +use frame_support::dispatch::Weight; +use frame_support::traits::{ConstU128, Everything, GenesisBuild}; +use frame_support::{ + assert_ok, construct_runtime, parameter_types, + traits::{ConstU32, ConstU64}, +}; +use frame_system::{ensure_signed, EnsureRoot}; +use hydradx_adapters::inspect::MultiInspectAdapter; +use hydradx_traits::Registry; +use orml_traits::parameter_type_with_key; +use pallet_currencies::BasicCurrencyAdapter; +use pallet_omnipool; +use pallet_omnipool::traits::ExternalPriceProvider; +use primitive_types::{U128, U256}; +use sp_core::H256; +use sp_runtime::traits::Zero; +use sp_runtime::Permill; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + DispatchError, DispatchResult, FixedU128, +}; +use std::cell::RefCell; +use std::collections::HashMap; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub type AccountId = u64; +pub type Balance = u128; +pub type AssetId = u32; + +pub const HDX: AssetId = 0; +pub const LRNA: AssetId = 1; +pub const DAI: AssetId = 2; + +pub const REGISTERED_ASSET: AssetId = 1000; + +pub const ONE: Balance = 1_000_000_000_000; + +pub const NATIVE_AMOUNT: Balance = 10_000 * ONE; + +thread_local! { + pub static POSITIONS: RefCell> = RefCell::new(HashMap::default()); + pub static REGISTERED_ASSETS: RefCell> = RefCell::new(HashMap::default()); + pub static ASSET_WEIGHT_CAP: RefCell = RefCell::new(Permill::from_percent(100)); + pub static ASSET_FEE: RefCell = RefCell::new(Permill::from_percent(0)); + pub static PROTOCOL_FEE: RefCell = RefCell::new(Permill::from_percent(0)); + pub static MIN_ADDED_LIQUDIITY: RefCell = RefCell::new(1000u128); + pub static MIN_TRADE_AMOUNT: RefCell = RefCell::new(1000u128); + pub static MAX_IN_RATIO: RefCell = RefCell::new(1u128); + pub static MAX_OUT_RATIO: RefCell = RefCell::new(1u128); + pub static MAX_PRICE_DIFF: RefCell = RefCell::new(Permill::from_percent(0)); + pub static EXT_PRICE_ADJUSTMENT: RefCell<(u32,u32, bool)> = RefCell::new((0u32,0u32, false)); + pub static WITHDRAWAL_FEE: RefCell = RefCell::new(Permill::from_percent(0)); + pub static WITHDRAWAL_ADJUSTMENT: RefCell<(u32,u32, bool)> = RefCell::new((0u32,0u32, false)); +} + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Omnipool: pallet_omnipool, + Tokens: orml_tokens, + RouteExecutor: pallet_route_executor, + Currencies: pallet_currencies, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = (); +} + +parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: AssetId| -> Balance { + 0 + }; +} + +impl orml_tokens::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type Amount = i128; + type CurrencyId = AssetId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type MaxLocks = (); + type DustRemovalWhitelist = Everything; + type MaxReserves = (); + type ReserveIdentifier = (); + type CurrencyHooks = (); +} + +parameter_types! { + pub const HDXAssetId: AssetId = HDX; + pub const LRNAAssetId: AssetId = LRNA; + pub const DAIAssetId: AssetId = DAI; + pub const PosiitionCollectionId: u32= 1000; + + pub ProtocolFee: Permill = PROTOCOL_FEE.with(|v| *v.borrow()); + pub AssetFee: Permill = ASSET_FEE.with(|v| *v.borrow()); + pub AssetWeightCap: Permill =ASSET_WEIGHT_CAP.with(|v| *v.borrow()); + pub MinAddedLiquidity: Balance = MIN_ADDED_LIQUDIITY.with(|v| *v.borrow()); + pub MinTradeAmount: Balance = MIN_TRADE_AMOUNT.with(|v| *v.borrow()); + pub MaxInRatio: Balance = MAX_IN_RATIO.with(|v| *v.borrow()); + pub MaxOutRatio: Balance = MAX_OUT_RATIO.with(|v| *v.borrow()); + pub const TVLCap: Balance = Balance::MAX; + pub MaxPriceDiff: Permill = MAX_PRICE_DIFF.with(|v| *v.borrow()); + pub FourPercentDiff: Permill = Permill::from_percent(4); + pub MinWithdrawFee: Permill = WITHDRAWAL_FEE.with(|v| *v.borrow()); +} + +impl pallet_omnipool::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AssetId = AssetId; + type PositionItemId = u32; + type Currency = Currencies; + type AuthorityOrigin = EnsureRoot; + type HubAssetId = LRNAAssetId; + type ProtocolFee = ProtocolFee; + type AssetFee = AssetFee; + type StableCoinAssetId = DAIAssetId; + type WeightInfo = (); + type HdxAssetId = HDXAssetId; + type NFTCollectionId = PosiitionCollectionId; + type NFTHandler = DummyNFT; + type AssetRegistry = DummyRegistry; + type MinimumTradingLimit = MinTradeAmount; + type MinimumPoolLiquidity = MinAddedLiquidity; + type TechnicalOrigin = EnsureRoot; + type MaxInRatio = MaxInRatio; + type MaxOutRatio = MaxOutRatio; + type CollectionId = u32; + type OmnipoolHooks = (); + type PriceBarrier = ( + EnsurePriceWithin, + EnsurePriceWithin, + ); + type MinWithdrawalFee = MinWithdrawFee; + type ExternalPriceOracle = WithdrawFeePriceOracle; +} + +impl pallet_currencies::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Tokens; + type NativeCurrency = BasicCurrencyAdapter; + type GetNativeCurrencyId = NativeCurrencyId; + type WeightInfo = (); +} + +pub const ASSET_PAIR_ACCOUNT: AccountId = 12; +//pub const ASSET_PAIR_ACCOUNT: [u8; 32] = [4u8; 32]; + +type OriginForRuntime = OriginFor; + +pub struct OmniPoolForRouter; + +impl TradeExecution for OmniPoolForRouter { + type Error = DispatchError; + + fn calculate_sell( + pool_type: PoolType, + asset_in: AssetId, + asset_out: AssetId, + amount_in: Balance, + ) -> Result> { + Omnipool::calculate_sell(pool_type, asset_in, asset_out, amount_in) + } + + fn calculate_buy( + pool_type: PoolType, + asset_in: AssetId, + asset_out: AssetId, + amount_out: Balance, + ) -> Result> { + Omnipool::calculate_buy(pool_type, asset_in, asset_out, amount_out) + } + + fn execute_sell( + who: OriginForRuntime, + pool_type: PoolType, + asset_in: AssetId, + asset_out: AssetId, + amount_in: Balance, + min_limit: Balance, + ) -> Result<(), ExecutorError> { + Omnipool::execute_sell(who, pool_type, asset_in, asset_out, amount_in, min_limit) + } + + fn execute_buy( + who: OriginForRuntime, + pool_type: PoolType, + asset_in: AssetId, + asset_out: AssetId, + amount_out: Balance, + max_limit: Balance, + ) -> Result<(), ExecutorError> { + Omnipool::execute_buy(who, pool_type, asset_in, asset_out, amount_out, max_limit) + } +} + +parameter_types! { + pub NativeCurrencyId: AssetId = HDX; +} + +type Pools = (OmniPoolForRouter); + +impl pallet_route_executor::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AssetId = AssetId; + type Balance = Balance; + type MaxNumberOfTrades = MaxNumberOfTrades; + type Currency = MultiInspectAdapter; + type AMM = Pools; + type WeightInfo = (); +} + +pub struct ExtBuilder { + endowed_accounts: Vec<(u64, AssetId, Balance)>, + registered_assets: Vec, + asset_fee: Permill, + protocol_fee: Permill, + asset_weight_cap: Permill, + min_liquidity: u128, + min_trade_limit: u128, + register_stable_asset: bool, + max_in_ratio: Balance, + max_out_ratio: Balance, + tvl_cap: Balance, + init_pool: Option<(FixedU128, FixedU128)>, + pool_tokens: Vec<(AssetId, FixedU128, AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + // If eg. tests running on one thread only, this thread local is shared. + // let's make sure that it is empty for each test case + // or set to original default value + REGISTERED_ASSETS.with(|v| { + v.borrow_mut().clear(); + }); + POSITIONS.with(|v| { + v.borrow_mut().clear(); + }); + ASSET_WEIGHT_CAP.with(|v| { + *v.borrow_mut() = Permill::from_percent(100); + }); + ASSET_FEE.with(|v| { + *v.borrow_mut() = Permill::from_percent(0); + }); + PROTOCOL_FEE.with(|v| { + *v.borrow_mut() = Permill::from_percent(0); + }); + MIN_ADDED_LIQUDIITY.with(|v| { + *v.borrow_mut() = 1000u128; + }); + MIN_TRADE_AMOUNT.with(|v| { + *v.borrow_mut() = 1000u128; + }); + MAX_IN_RATIO.with(|v| { + *v.borrow_mut() = 1u128; + }); + MAX_OUT_RATIO.with(|v| { + *v.borrow_mut() = 1u128; + }); + MAX_PRICE_DIFF.with(|v| { + *v.borrow_mut() = Permill::from_percent(0); + }); + EXT_PRICE_ADJUSTMENT.with(|v| { + *v.borrow_mut() = (0, 0, false); + }); + WITHDRAWAL_FEE.with(|v| { + *v.borrow_mut() = Permill::from_percent(0); + }); + WITHDRAWAL_ADJUSTMENT.with(|v| { + *v.borrow_mut() = (0, 0, false); + }); + + Self { + endowed_accounts: vec![ + (Omnipool::protocol_account(), DAI, 1000 * ONE), + (Omnipool::protocol_account(), HDX, NATIVE_AMOUNT), + ], + asset_fee: Permill::from_percent(0), + protocol_fee: Permill::from_percent(0), + asset_weight_cap: Permill::from_percent(100), + min_liquidity: 0, + registered_assets: vec![], + min_trade_limit: 0, + init_pool: None, + register_stable_asset: true, + pool_tokens: vec![], + max_in_ratio: 1u128, + max_out_ratio: 1u128, + tvl_cap: u128::MAX, + } + } +} + +impl ExtBuilder { + pub fn with_endowed_accounts(mut self, accounts: Vec<(u64, AssetId, Balance)>) -> Self { + self.endowed_accounts = accounts; + self + } + pub fn with_initial_pool(mut self, stable_price: FixedU128, native_price: FixedU128) -> Self { + self.init_pool = Some((stable_price, native_price)); + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + // Add DAi and HDX as pre-registered assets + REGISTERED_ASSETS.with(|v| { + if self.register_stable_asset { + v.borrow_mut().insert(DAI, DAI); + } + v.borrow_mut().insert(HDX, HDX); + v.borrow_mut().insert(REGISTERED_ASSET, REGISTERED_ASSET); + self.registered_assets.iter().for_each(|asset| { + v.borrow_mut().insert(*asset, *asset); + }); + }); + + ASSET_FEE.with(|v| { + *v.borrow_mut() = self.asset_fee; + }); + ASSET_WEIGHT_CAP.with(|v| { + *v.borrow_mut() = self.asset_weight_cap; + }); + + PROTOCOL_FEE.with(|v| { + *v.borrow_mut() = self.protocol_fee; + }); + + MIN_ADDED_LIQUDIITY.with(|v| { + *v.borrow_mut() = self.min_liquidity; + }); + + MIN_TRADE_AMOUNT.with(|v| { + *v.borrow_mut() = self.min_trade_limit; + }); + MAX_IN_RATIO.with(|v| { + *v.borrow_mut() = self.max_in_ratio; + }); + MAX_OUT_RATIO.with(|v| { + *v.borrow_mut() = self.max_out_ratio; + }); + + let mut initial_native_accounts: Vec<(AccountId, Balance)> = vec![(ASSET_PAIR_ACCOUNT, 10000 * ONE)]; + let additional_accounts: Vec<(AccountId, Balance)> = self + .endowed_accounts + .iter() + .filter(|a| a.1 == HDX) + .flat_map(|(x, _, amount)| vec![(*x, *amount)]) + .collect::<_>(); + + initial_native_accounts.extend(additional_accounts); + + pallet_balances::GenesisConfig:: { + balances: initial_native_accounts, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut initial_accounts = vec![ + (ASSET_PAIR_ACCOUNT, LRNA, 10000 * ONE), + (ASSET_PAIR_ACCOUNT, DAI, 10000 * ONE), + ]; + + initial_accounts.extend(self.endowed_accounts); + + orml_tokens::GenesisConfig:: { + balances: initial_accounts, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut r: sp_io::TestExternalities = t.into(); + + r.execute_with(|| { + assert_ok!(Omnipool::set_tvl_cap(RuntimeOrigin::root(), self.tvl_cap,)); + }); + + if let Some((stable_price, native_price)) = self.init_pool { + r.execute_with(|| { + assert_ok!(Omnipool::initialize_pool( + RuntimeOrigin::root(), + stable_price, + native_price, + Permill::from_percent(100), + Permill::from_percent(100) + )); + + for (asset_id, price, owner, amount) in self.pool_tokens { + assert_ok!(Tokens::transfer( + RuntimeOrigin::signed(owner), + Omnipool::protocol_account(), + asset_id, + amount + )); + assert_ok!(Omnipool::add_token( + RuntimeOrigin::root(), + asset_id, + price, + self.asset_weight_cap, + owner + )); + } + }); + } + + r + } +} + +use frame_support::traits::tokens::nonfungibles::{Create, Inspect, Mutate}; +use frame_system::pallet_prelude::OriginFor; +use hydra_dx_math::ema::EmaPrice; +use hydra_dx_math::support::rational::Rounding; +use hydra_dx_math::to_u128_wrapper; +use hydradx_traits::router::{ExecutorError, PoolType, TradeExecution}; +use pallet_omnipool::traits::EnsurePriceWithin; + +pub struct DummyNFT; + +impl> Inspect for DummyNFT { + type ItemId = u32; + type CollectionId = u32; + + fn owner(_class: &Self::CollectionId, instance: &Self::ItemId) -> Option { + let mut owner: Option = None; + + POSITIONS.with(|v| { + if let Some(o) = v.borrow().get(instance) { + owner = Some((*o).into()); + } + }); + owner + } +} + +impl> Create for DummyNFT { + fn create_collection(_class: &Self::CollectionId, _who: &AccountId, _admin: &AccountId) -> DispatchResult { + Ok(()) + } +} + +impl + Into + Copy> Mutate for DummyNFT { + fn mint_into(_class: &Self::CollectionId, _instance: &Self::ItemId, _who: &AccountId) -> DispatchResult { + POSITIONS.with(|v| { + let mut m = v.borrow_mut(); + m.insert(*_instance, (*_who).into()); + }); + Ok(()) + } + + fn burn( + _class: &Self::CollectionId, + instance: &Self::ItemId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { + POSITIONS.with(|v| { + let mut m = v.borrow_mut(); + m.remove(instance); + }); + Ok(()) + } +} + +pub struct DummyRegistry(sp_std::marker::PhantomData); + +impl Registry, Balance, DispatchError> for DummyRegistry +where + T::AssetId: Into + From, +{ + fn exists(asset_id: T::AssetId) -> bool { + let asset = REGISTERED_ASSETS.with(|v| v.borrow().get(&(asset_id.into())).copied()); + matches!(asset, Some(_)) + } + + fn retrieve_asset(_name: &Vec) -> Result { + Ok(T::AssetId::default()) + } + + fn create_asset(_name: &Vec, _existential_deposit: Balance) -> Result { + let assigned = REGISTERED_ASSETS.with(|v| { + let l = v.borrow().len(); + v.borrow_mut().insert(l as u32, l as u32); + l as u32 + }); + Ok(T::AssetId::from(assigned)) + } +} + +pub struct MockOracle; + +impl ExternalPriceProvider for MockOracle { + type Error = DispatchError; + + fn get_price(asset_a: AssetId, asset_b: AssetId) -> Result { + assert_eq!(asset_a, LRNA); + let asset_state = Omnipool::load_asset_state(asset_b)?; + let price = EmaPrice::new(asset_state.hub_reserve, asset_state.reserve); + let adjusted_price = EXT_PRICE_ADJUSTMENT.with(|v| { + let (n, d, neg) = *v.borrow(); + let adjustment = EmaPrice::new(price.n * n as u128, price.d * d as u128); + if neg { + saturating_sub(price, adjustment) + } else { + saturating_add(price, adjustment) + } + }); + + Ok(adjusted_price) + } + + fn get_price_weight() -> Weight { + todo!() + } +} + +pub struct WithdrawFeePriceOracle; + +impl ExternalPriceProvider for WithdrawFeePriceOracle { + type Error = DispatchError; + + fn get_price(asset_a: AssetId, asset_b: AssetId) -> Result { + assert_eq!(asset_a, LRNA); + let asset_state = Omnipool::load_asset_state(asset_b)?; + let price = EmaPrice::new(asset_state.hub_reserve, asset_state.reserve); + + let adjusted_price = WITHDRAWAL_ADJUSTMENT.with(|v| { + let (n, d, neg) = *v.borrow(); + let adjustment = EmaPrice::new(price.n * n as u128, price.d * d as u128); + if neg { + saturating_sub(price, adjustment) + } else { + saturating_add(price, adjustment) + } + }); + + Ok(adjusted_price) + } + + fn get_price_weight() -> Weight { + todo!() + } +} + +// Helper methods to work with Ema Price +pub(super) fn round_to_rational((n, d): (U256, U256), rounding: Rounding) -> EmaPrice { + let shift = n.bits().max(d.bits()).saturating_sub(128); + let (n, d) = if shift > 0 { + let min_n = u128::from(!n.is_zero()); + let (bias_n, bias_d) = rounding.to_bias(1); + let shifted_n = (n >> shift).low_u128(); + let shifted_d = (d >> shift).low_u128(); + ( + shifted_n.saturating_add(bias_n).max(min_n), + shifted_d.saturating_add(bias_d).max(1), + ) + } else { + (n.low_u128(), d.low_u128()) + }; + EmaPrice::new(n, d) +} + +pub(super) fn saturating_add(l: EmaPrice, r: EmaPrice) -> EmaPrice { + if l.n.is_zero() || r.n.is_zero() { + return EmaPrice::new(l.n, l.d); + } + let (l_n, l_d, r_n, r_d) = to_u128_wrapper!(l.n, l.d, r.n, r.d); + // n = l.n * r.d - r.n * l.d + let n = l_n.full_mul(r_d).saturating_add(r_n.full_mul(l_d)); + // d = l.d * r.d + let d = l_d.full_mul(r_d); + round_to_rational((n, d), Rounding::Nearest) +} + +pub(super) fn saturating_sub(l: EmaPrice, r: EmaPrice) -> EmaPrice { + if l.n.is_zero() || r.n.is_zero() { + return EmaPrice::new(l.n, l.d); + } + let (l_n, l_d, r_n, r_d) = to_u128_wrapper!(l.n, l.d, r.n, r.d); + // n = l.n * r.d - r.n * l.d + let n = l_n.full_mul(r_d).saturating_sub(r_n.full_mul(l_d)); + // d = l.d * r.d + let d = l_d.full_mul(r_d); + round_to_rational((n, d), Rounding::Nearest) +} diff --git a/runtime/hydradx/src/xcm/tests/mod.rs b/runtime/hydradx/src/xcm/tests/mod.rs new file mode 100644 index 000000000..265d91e0c --- /dev/null +++ b/runtime/hydradx/src/xcm/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod mock; +pub mod xcm_exchange; diff --git a/pallets/omnipool/src/tests/xcm_exchange.rs b/runtime/hydradx/src/xcm/tests/xcm_exchange.rs similarity index 86% rename from pallets/omnipool/src/tests/xcm_exchange.rs rename to runtime/hydradx/src/xcm/tests/xcm_exchange.rs index 81c5eb735..c18237687 100644 --- a/pallets/omnipool/src/tests/xcm_exchange.rs +++ b/runtime/hydradx/src/xcm/tests/xcm_exchange.rs @@ -1,14 +1,15 @@ -use super::*; -use crate::tests::mock::AssetId as CurrencyId; -use crate::tests::mock::{Balances, Tokens}; -use crate::xcm_exchange::OmniExchanger; +use crate::xcm::tests::mock::AccountId; +use crate::xcm::tests::mock::AssetId as CurrencyId; +use crate::xcm::tests::mock::*; +use crate::xcm::tests::mock::{Balances, Tokens}; +use crate::xcm::xcm_exchange::OmniExchanger; use frame_support::{assert_noop, parameter_types}; +use orml_traits::MultiCurrency; use polkadot_xcm::latest::prelude::*; use pretty_assertions::assert_eq; use sp_runtime::traits::Convert; -use sp_runtime::SaturatedConversion; +use sp_runtime::{FixedU128, SaturatedConversion}; use xcm_executor::traits::AssetExchange; - parameter_types! { pub ExchangeTempAccount: AccountId = 12345; } @@ -59,9 +60,10 @@ fn omni_exchanger_allows_selling_supported_assets() { let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees let want = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); // Act - let received = - OmniExchanger::::exchange_asset(None, give, &want, SELL) - .expect("should return ok"); + let received = OmniExchanger::::exchange_asset( + None, give, &want, SELL, + ) + .expect("should return ok"); // Assert let mut iter = received.fungible_assets_iter(); let asset_received = iter.next().expect("there should be at least one asset"); @@ -91,9 +93,10 @@ fn omni_exchanger_allows_buying_supported_assets() { let want_asset = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)); let want = want_asset.clone().into(); // Act - let received = - OmniExchanger::::exchange_asset(None, give, &want, BUY) - .expect("should return ok"); + let received = OmniExchanger::::exchange_asset( + None, give, &want, BUY, + ) + .expect("should return ok"); // Assert let mut iter = received.fungible_assets_iter(); let asset_received = iter.next().expect("there should be at least one asset"); @@ -129,7 +132,7 @@ fn omni_exchanger_should_not_allow_trading_for_multiple_assets() { // Act and assert assert_noop!( - OmniExchanger::::exchange_asset( + OmniExchanger::::exchange_asset( None, give.clone().into(), &want, diff --git a/runtime/hydradx/src/xcm/xcm_exchange.rs b/runtime/hydradx/src/xcm/xcm_exchange.rs index f9f90556c..9ced48ec3 100644 --- a/runtime/hydradx/src/xcm/xcm_exchange.rs +++ b/runtime/hydradx/src/xcm/xcm_exchange.rs @@ -1,19 +1,27 @@ use common_runtime::AccountId; +use hydradx_traits::router::PoolType::Omnipool; +use orml_traits::MultiCurrency; +use pallet_route_executor::Trade; use polkadot_xcm::latest::prelude::*; use sp_core::Get; -use sp_runtime::traits::Convert; +use sp_runtime::traits::{Convert, Zero}; use sp_std::marker::PhantomData; +use sp_std::vec; use xcm_executor::traits::AssetExchange; - //TODO: copy unit tests from omnipool and adapt them -pub struct OmniExchanger(PhantomData<(T, TempAccount, CurrencyIdConvert)>); +pub struct OmniExchanger( + PhantomData<(T, TempAccount, CurrencyIdConvert, Currency)>, +); -impl AssetExchange for OmniExchanger +impl AssetExchange + for OmniExchanger where - T: pallet_omnipool::Config, + T: pallet_route_executor::Config, TempAccount: Get, CurrencyIdConvert: Convert>, + Currency: MultiCurrency, + T::Balance: From + Zero + Into, { fn exchange_asset( origin: Option<&MultiLocation>, @@ -21,7 +29,6 @@ where want: &MultiAssets, maximal: bool, ) -> Result { - use orml_traits::MultiCurrency; use orml_utilities::with_transaction_result; let account = if origin.is_none() { @@ -55,19 +62,30 @@ where let Fungible(min_buy_amount) = wanted.fun else { return Err(give) }; with_transaction_result(|| { - T::Currency::deposit(asset_in, &account, amount)?; // mint the incoming tokens - pallet_omnipool::Pallet::::sell(origin, asset_in, asset_out, amount, min_buy_amount)?; + Currency::deposit(asset_in, &account, amount.into())?; // mint the incoming tokens + pallet_route_executor::Pallet::::sell( + origin, + asset_in, + asset_out, + amount.into(), + min_buy_amount.into(), + vec![Trade { + pool: Omnipool, + asset_in, + asset_out, + }], + )?; debug_assert!( - T::Currency::free_balance(asset_in, &account) == 0, + Currency::free_balance(asset_in, &account) == T::Balance::zero(), "Sell should not leave any of the incoming asset." ); - let amount_received = T::Currency::free_balance(asset_out, &account); + let amount_received = Currency::free_balance(asset_out, &account); debug_assert!( - amount_received >= min_buy_amount, + amount_received >= min_buy_amount.into(), "Sell should return more than mininum buy amount." ); - T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens - Ok(MultiAsset::from((wanted.id, amount_received)).into()) + Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens + Ok(MultiAsset::from((wanted.id, amount_received.into())).into()) }) .map_err(|_| give) } else { @@ -76,21 +94,32 @@ where let Fungible(max_sell_amount) = given.fun else { return Err(give) }; with_transaction_result(|| { - T::Currency::deposit(asset_in, &account, max_sell_amount)?; // mint the incoming tokens - pallet_omnipool::Pallet::::buy(origin, asset_out, asset_in, amount, max_sell_amount)?; + Currency::deposit(asset_in, &account, max_sell_amount.into())?; // mint the incoming tokens + pallet_route_executor::Pallet::::buy( + origin, + asset_in, + asset_out, + amount.into(), + max_sell_amount.into(), + vec![Trade { + pool: Omnipool, + asset_in, + asset_out, + }], + )?; let mut assets = sp_std::vec::Vec::new(); - let left_over = T::Currency::free_balance(asset_in, &account); - if left_over > 0 { - T::Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens - assets.push(MultiAsset::from((given.id, left_over))); + let left_over = Currency::free_balance(asset_in, &account); + if left_over > T::Balance::zero() { + Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens + assets.push(MultiAsset::from((given.id, left_over.into()))); } - let amount_received = T::Currency::free_balance(asset_out, &account); + let amount_received = Currency::free_balance(asset_out, &account); debug_assert!( - amount_received == amount, + amount_received == amount.into(), "Buy should return exactly the amount we specified." ); - T::Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens - assets.push(MultiAsset::from((wanted.id, amount_received))); + Currency::withdraw(asset_out, &account, amount_received)?; // burn the received tokens + assets.push(MultiAsset::from((wanted.id, amount_received.into()))); Ok(assets.into()) }) .map_err(|_| give) From 0c2eae9a12433278bb4e64ea492a5b5c8110ec81 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 19:39:51 +0200 Subject: [PATCH 42/73] remove done todo comment --- runtime/hydradx/src/xcm/xcm_exchange.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/hydradx/src/xcm/xcm_exchange.rs b/runtime/hydradx/src/xcm/xcm_exchange.rs index 087a5ccef..318cf4589 100644 --- a/runtime/hydradx/src/xcm/xcm_exchange.rs +++ b/runtime/hydradx/src/xcm/xcm_exchange.rs @@ -7,7 +7,6 @@ use sp_runtime::traits::{Convert, Zero}; use sp_std::marker::PhantomData; use sp_std::vec; use xcm_executor::traits::AssetExchange; -//TODO: copy unit tests from omnipool and adapt them pub struct OmniExchanger( PhantomData<(T, TempAccount, CurrencyIdConvert, Currency)>, From fe94afe103b2ebc2fc094c879bf9d30ab7c57746 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:25:50 +0200 Subject: [PATCH 43/73] replace xcm filter and omniexchange to adapter --- Cargo.lock | 10 + runtime/adapters/Cargo.toml | 15 + runtime/adapters/src/filters.rs | 90 +++++ runtime/adapters/src/lib.rs | 2 + .../src/xcm => adapters/src}/tests/mock.rs | 10 +- .../src/xcm => adapters/src}/tests/mod.rs | 2 + .../src/{tests.rs => tests/trader.rs} | 2 +- .../src}/tests/xcm_exchange.rs | 10 +- runtime/adapters/src/tests/xcm_filter.rs | 277 +++++++++++++ .../src/xcm => adapters/src}/xcm_exchange.rs | 0 runtime/hydradx/src/xcm.rs | 10 +- runtime/hydradx/src/xcm/filters.rs | 369 ------------------ 12 files changed, 409 insertions(+), 388 deletions(-) create mode 100644 runtime/adapters/src/filters.rs rename runtime/{hydradx/src/xcm => adapters/src}/tests/mock.rs (99%) rename runtime/{hydradx/src/xcm => adapters/src}/tests/mod.rs (50%) rename runtime/adapters/src/{tests.rs => tests/trader.rs} (99%) rename runtime/{hydradx/src/xcm => adapters/src}/tests/xcm_exchange.rs (95%) create mode 100644 runtime/adapters/src/tests/xcm_filter.rs rename runtime/{hydradx/src/xcm => adapters/src}/xcm_exchange.rs (100%) delete mode 100644 runtime/hydradx/src/xcm/filters.rs diff --git a/Cargo.lock b/Cargo.lock index 3675e12ff..d40dd9196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3774,24 +3774,34 @@ dependencies = [ name = "hydradx-adapters" version = "0.4.0" dependencies = [ + "cumulus-primitives-core", "frame-support", "frame-system", "hydra-dx-math", "hydradx-traits", "lazy_static", "log", + "orml-tokens", "orml-traits", + "orml-utilities", "orml-xcm-support", + "pallet-balances", "pallet-circuit-breaker", + "pallet-currencies", "pallet-dynamic-fees", "pallet-ema-oracle", "pallet-liquidity-mining", "pallet-omnipool", "pallet-omnipool-liquidity-mining", + "pallet-route-executor", "pallet-transaction-multi-payment", "parity-scale-codec", + "pretty_assertions", "primitive-types", "primitives", + "scale-info", + "sp-core", + "sp-io", "sp-runtime", "sp-std", "xcm", diff --git a/runtime/adapters/Cargo.toml b/runtime/adapters/Cargo.toml index 1efea0a61..1dab41909 100644 --- a/runtime/adapters/Cargo.toml +++ b/runtime/adapters/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/galacticcouncil/warehouse/tree/master/adapters" [dependencies] codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.3.1", default-features = false, features = ["derive"] } # HydraDX dependencies primitives = { workspace = true } @@ -22,6 +23,8 @@ pallet-circuit-breaker = { workspace = true } warehouse-liquidity-mining = { workspace = true } pallet-omnipool-liquidity-mining = { workspace = true } pallet-dynamic-fees = { workspace = true } +pallet-route-executor = { workspace = true } +pallet-currencies = { workspace = true } # Substrate dependencies frame-support = { workspace = true } @@ -29,6 +32,8 @@ frame-system = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } primitive-types = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } # Polkadot dependencies polkadot-xcm = { workspace = true } @@ -38,9 +43,19 @@ xcm-executor = { workspace = true } # ORML dependencies orml-xcm-support = { workspace = true } orml-traits = { workspace = true } +orml-utilities = { workspace = true } +orml-tokens = { workspace = true } + +# Pallets +pallet-balances = { workspace = true } + +# Cumulus dependencies +cumulus-primitives-core = { workspace = true } + [dev-dependencies] lazy_static = { features = ["spin_no_std"], version = "1.4.0" } +pretty_assertions = "1.4.0" [features] default = ["std"] diff --git a/runtime/adapters/src/filters.rs b/runtime/adapters/src/filters.rs new file mode 100644 index 000000000..9ef4648ac --- /dev/null +++ b/runtime/adapters/src/filters.rs @@ -0,0 +1,90 @@ +use core::{cell::Cell, marker::PhantomData}; + +use frame_support::traits::Contains; +use polkadot_xcm::v3::prelude::*; +use sp_core::Get; +use sp_runtime::Either; + +pub struct AllowTransferAndSwap( + PhantomData<(MaxXcmDepth, MaxInstructions, RuntimeCall)>, +) +where + MaxXcmDepth: Get, + MaxInstructions: Get; + +fn allowed_or_recurse(inst: &Instruction) -> Either> { + match inst { + ClearOrigin + | ClaimAsset { .. } + | ExchangeAsset { .. } + | WithdrawAsset(..) + | TransferAsset { .. } + | DepositAsset { .. } + | SetTopic(..) + | ClearTopic + | ExpectAsset(..) + | BurnAsset(..) + | SetFeesMode { .. } + | BuyExecution { .. } => Either::Left(true), + InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { + Either::Right(xcm) + } + _ => Either::Left(false), + } +} + +fn check_instructions_recursively( + xcm: &Xcm, + depth: u16, + instructions: &Cell, //TODO: don't use std, use core or sp_std for Cell +) -> bool +where + MaxXcmDepth: Get, + MaxInstructions: Get, +{ + if depth > MaxXcmDepth::get() { + return false; + } + let instructions_count = instructions; //TODO: use just a let mut u16 + let mut iter = xcm.inner().iter(); + while let Some(inst) = iter.next() { + instructions_count.set(instructions_count.get() + 1); + if instructions_count.get() > MaxInstructions::get() { + return false; + } + + match allowed_or_recurse(inst) { + Either::Left(true) => continue, + Either::Left(false) => return false, + Either::Right(xcm) => { + if check_instructions_recursively::( + xcm, + depth + 1, + instructions_count, + ) { + continue; + } else { + return false; + } + } + } + } + true +} + +impl Contains<(MultiLocation, Xcm)> + for AllowTransferAndSwap +where + MaxXcmDepth: Get, + MaxInstructions: Get, +{ + fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { + // allow root to execute XCM + if loc == &MultiLocation::here() { + return true; + } + + let instructions_count = Cell::new(0u16); + check_instructions_recursively::(xcm, 0, &instructions_count) + } +} diff --git a/runtime/adapters/src/lib.rs b/runtime/adapters/src/lib.rs index aa2b089f9..f001c06ad 100644 --- a/runtime/adapters/src/lib.rs +++ b/runtime/adapters/src/lib.rs @@ -51,7 +51,9 @@ use xcm_executor::{ Assets, }; +pub mod filters; pub mod inspect; +pub mod xcm_exchange; #[cfg(test)] mod tests; diff --git a/runtime/hydradx/src/xcm/tests/mock.rs b/runtime/adapters/src/tests/mock.rs similarity index 99% rename from runtime/hydradx/src/xcm/tests/mock.rs rename to runtime/adapters/src/tests/mock.rs index 87a3d890d..00f9d5d77 100644 --- a/runtime/hydradx/src/xcm/tests/mock.rs +++ b/runtime/adapters/src/tests/mock.rs @@ -17,17 +17,16 @@ //! Test environment for Assets pallet. -use crate::Amount; -use crate::MaxNumberOfTrades; +use primitives::Amount; +use crate::inspect::MultiInspectAdapter; use frame_support::dispatch::Weight; use frame_support::traits::{ConstU128, Everything, GenesisBuild}; use frame_support::{ assert_ok, construct_runtime, parameter_types, traits::{ConstU32, ConstU64}, }; -use frame_system::{ensure_signed, EnsureRoot}; -use hydradx_adapters::inspect::MultiInspectAdapter; +use frame_system::EnsureRoot; use hydradx_traits::Registry; use orml_traits::{parameter_type_with_key, GetByKey}; use pallet_currencies::BasicCurrencyAdapter; @@ -158,6 +157,7 @@ parameter_types! { pub const DAIAssetId: AssetId = DAI; pub const PosiitionCollectionId: u32= 1000; + pub const MaxNumberOfTrades: u8 = 5; pub ProtocolFee: Permill = PROTOCOL_FEE.with(|v| *v.borrow()); pub AssetFee: Permill = ASSET_FEE.with(|v| *v.borrow()); pub AssetWeightCap: Permill =ASSET_WEIGHT_CAP.with(|v| *v.borrow()); @@ -271,7 +271,7 @@ parameter_types! { pub NativeCurrencyId: AssetId = HDX; } -type Pools = (OmniPoolForRouter); +type Pools = OmniPoolForRouter; impl pallet_route_executor::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/runtime/hydradx/src/xcm/tests/mod.rs b/runtime/adapters/src/tests/mod.rs similarity index 50% rename from runtime/hydradx/src/xcm/tests/mod.rs rename to runtime/adapters/src/tests/mod.rs index 265d91e0c..57a6b371d 100644 --- a/runtime/hydradx/src/xcm/tests/mod.rs +++ b/runtime/adapters/src/tests/mod.rs @@ -1,2 +1,4 @@ pub mod mock; +pub mod trader; pub mod xcm_exchange; +pub mod xcm_filter; diff --git a/runtime/adapters/src/tests.rs b/runtime/adapters/src/tests/trader.rs similarity index 99% rename from runtime/adapters/src/tests.rs rename to runtime/adapters/src/tests/trader.rs index 885c6ea94..d5fe83e2c 100644 --- a/runtime/adapters/src/tests.rs +++ b/runtime/adapters/src/tests/trader.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; +use crate::*; use codec::{Decode, Encode}; use frame_support::{weights::IdentityFee, BoundedVec}; use sp_runtime::{traits::One, DispatchResult, FixedU128}; diff --git a/runtime/hydradx/src/xcm/tests/xcm_exchange.rs b/runtime/adapters/src/tests/xcm_exchange.rs similarity index 95% rename from runtime/hydradx/src/xcm/tests/xcm_exchange.rs rename to runtime/adapters/src/tests/xcm_exchange.rs index c18237687..71a870497 100644 --- a/runtime/hydradx/src/xcm/tests/xcm_exchange.rs +++ b/runtime/adapters/src/tests/xcm_exchange.rs @@ -1,8 +1,8 @@ -use crate::xcm::tests::mock::AccountId; -use crate::xcm::tests::mock::AssetId as CurrencyId; -use crate::xcm::tests::mock::*; -use crate::xcm::tests::mock::{Balances, Tokens}; -use crate::xcm::xcm_exchange::OmniExchanger; +use crate::tests::mock::AccountId; +use crate::tests::mock::AssetId as CurrencyId; +use crate::tests::mock::*; +use crate::tests::mock::{DAI, HDX, NATIVE_AMOUNT}; +use crate::xcm_exchange::OmniExchanger; use frame_support::{assert_noop, parameter_types}; use orml_traits::MultiCurrency; use polkadot_xcm::latest::prelude::*; diff --git a/runtime/adapters/src/tests/xcm_filter.rs b/runtime/adapters/src/tests/xcm_filter.rs new file mode 100644 index 000000000..68544914a --- /dev/null +++ b/runtime/adapters/src/tests/xcm_filter.rs @@ -0,0 +1,277 @@ +use crate::filters::AllowTransferAndSwap; +use crate::tests::mock::*; +use codec::Encode; +use frame_support::pallet_prelude::Weight; +use frame_support::traits::Contains; +use polkadot_xcm::prelude::*; +use sp_runtime::traits::ConstU16; + +//TODO: consider what others needs to be filtered out then add them to this test +#[test] +fn xcm_execute_filter_should_not_allow_transact() { + let call = RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); + let xcm = Xcm(vec![Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1, 1), + call: call.into(), + }]); + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + assert!(xcm_execute_filter_does_not_allow(&(loc, xcm))); +} + +#[test] +fn xcm_execute_filter_should_allow_a_transfer_and_swap() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let weight_limit = WeightLimit::Unlimited; + let give: MultiAssetFilter = fees.clone().into(); + let want: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + ExchangeAsset { + give, + want, + maximal: true, + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]); + + let message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + TransferReserveAsset { assets, dest, xcm }, + ]); + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(xcm_execute_filter_allows(&(loc, message))); +} + +#[test] +fn xcm_execute_filter_should_filter_too_deep_xcm() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit, + }]); + + for _ in 0..5 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm, + }]); + } + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(xcm_execute_filter_does_not_allow(&(loc, message))); +} + +#[test] +fn xcm_execute_filter_should_not_filter_message_with_max_deep() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit, + }]); + + for _ in 0..4 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm, + }]); + } + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( + loc, message + ))); +} + +#[test] +fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() { + //Arrange + let fees = MultiAsset::from((MultiLocation::here(), 10)); + let assets: MultiAssets = fees.clone().into(); + + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + let dest = MultiLocation::new(1, Parachain(2047)); + + let deposit = Xcm(vec![DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }]); + + let mut message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: deposit.clone(), + }]); + + for _ in 0..2 { + let xcm = message.clone(); + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: xcm.clone(), + }]); + } + + //It has 5 instruction + let mut instructions_with_inner_xcms: Vec> = vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: message.clone(), + }]; + + let mut rest: Vec> = vec![ + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }; + 95 + ]; + + instructions_with_inner_xcms.append(&mut rest); + + message = Xcm(vec![TransferReserveAsset { + assets: assets.clone(), + dest, + xcm: Xcm(instructions_with_inner_xcms.clone()), + }]); + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(xcm_execute_filter_does_not_allow(&(loc, message))); +} + +#[test] +fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_one_level() { + //Arrange + let max_assets = 2; + let beneficiary = Junction::AccountId32 { + id: [3; 32], + network: None, + } + .into(); + + let message_with_more_instructions_than_allowed = Xcm(vec![ + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }; + 101 + ]); + + let loc = MultiLocation::new( + 0, + AccountId32 { + network: None, + id: [1; 32], + }, + ); + + //Act and assert + assert!(xcm_execute_filter_does_not_allow(&( + loc, + message_with_more_instructions_than_allowed + ))); +} + +fn xcm_execute_filter_allows(loc_and_message: &(MultiLocation, Xcm)) -> bool { + AllowTransferAndSwap::, ConstU16<100>, RuntimeCall>::contains(loc_and_message) +} + +fn xcm_execute_filter_does_not_allow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool { + !AllowTransferAndSwap::, ConstU16<100>, ()>::contains(loc_and_message) +} diff --git a/runtime/hydradx/src/xcm/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs similarity index 100% rename from runtime/hydradx/src/xcm/xcm_exchange.rs rename to runtime/adapters/src/xcm_exchange.rs diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 58ad7e55a..82558467f 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -27,13 +27,7 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; -mod filters; -mod xcm_exchange; - -#[cfg(test)] -mod tests; - -pub use filters::*; +use hydradx_adapters::filters::AllowTransferAndSwap; #[derive(Debug, Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct AssetLocation(pub polkadot_xcm::v3::MultiLocation); @@ -226,7 +220,7 @@ impl pallet_xcm::Config for Runtime { } pub struct CurrencyIdConvert; -use crate::xcm::xcm_exchange::OmniExchanger; +use hydradx_adapters::xcm_exchange::OmniExchanger; use primitives::constants::chain::CORE_ASSET_ID; impl Convert> for CurrencyIdConvert { diff --git a/runtime/hydradx/src/xcm/filters.rs b/runtime/hydradx/src/xcm/filters.rs deleted file mode 100644 index 675e37dc0..000000000 --- a/runtime/hydradx/src/xcm/filters.rs +++ /dev/null @@ -1,369 +0,0 @@ -use core::{cell::Cell, marker::PhantomData}; - -use frame_support::traits::Contains; -use polkadot_xcm::v3::prelude::*; -use sp_core::{ConstU16, Get}; -use sp_runtime::Either; - -pub struct AllowTransferAndSwap( - PhantomData<(MaxXcmDepth, MaxInstructions, RuntimeCall)>, -) -where - MaxXcmDepth: Get, - MaxInstructions: Get; - -fn allowed_or_recurse(inst: &Instruction) -> Either> { - match inst { - ClearOrigin - | ClaimAsset { .. } - | ExchangeAsset { .. } - | WithdrawAsset(..) - | TransferAsset { .. } - | DepositAsset { .. } - | SetTopic(..) - | ClearTopic - | ExpectAsset(..) - | BurnAsset(..) - | SetFeesMode { .. } - | BuyExecution { .. } => Either::Left(true), - InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { - Either::Right(xcm) - } - _ => Either::Left(false), - } -} - -fn check_instructions_recursively( - xcm: &Xcm, - depth: u16, - instructions: &Cell, //TODO: don't use std, use core or sp_std for Cell -) -> bool -where - MaxXcmDepth: Get, - MaxInstructions: Get, -{ - if depth > MaxXcmDepth::get() { - return false; - } - let instructions_count = instructions; //TODO: use just a let mut u16 - let mut iter = xcm.inner().iter(); - while let Some(inst) = iter.next() { - instructions_count.set(instructions_count.get() + 1); - if instructions_count.get() > MaxInstructions::get() { - return false; - } - - match allowed_or_recurse(inst) { - Either::Left(true) => continue, - Either::Left(false) => return false, - Either::Right(xcm) => { - if check_instructions_recursively::( - xcm, - depth + 1, - instructions_count, - ) { - continue; - } else { - return false; - } - } - } - } - true -} - -impl Contains<(MultiLocation, Xcm)> - for AllowTransferAndSwap -where - MaxXcmDepth: Get, - MaxInstructions: Get, -{ - fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { - // allow root to execute XCM - if loc == &MultiLocation::here() { - return true; - } - - let instructions_count = Cell::new(0u16); - check_instructions_recursively::(xcm, 0, &instructions_count) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use codec::Encode; - use frame_support::pallet_prelude::Weight; - - //TODO: consider what others needs to be filtered out then add them to this test - #[test] - fn xcm_execute_filter_should_not_allow_transact() { - let call = crate::RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }).encode(); - let xcm = Xcm(vec![Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: Weight::from_parts(1, 1), - call: call.into(), - }]); - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - assert!(xcm_execute_filter_does_not_allow(&(loc, xcm))); - } - - #[test] - fn xcm_execute_filter_should_allow_a_transfer_and_swap() { - //Arrange - let fees = MultiAsset::from((MultiLocation::here(), 10)); - let weight_limit = WeightLimit::Unlimited; - let give: MultiAssetFilter = fees.clone().into(); - let want: MultiAssets = fees.clone().into(); - let assets: MultiAssets = fees.clone().into(); - - let max_assets = 2; - let beneficiary = Junction::AccountId32 { - id: [3; 32], - network: None, - } - .into(); - let dest = MultiLocation::new(1, Parachain(2047)); - - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, - ExchangeAsset { - give, - want, - maximal: true, - }, - DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }, - ]); - - let message = Xcm(vec![ - SetFeesMode { jit_withdraw: true }, - TransferReserveAsset { assets, dest, xcm }, - ]); - - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - - //Act and assert - assert!(xcm_execute_filter_allows(&(loc, message))); - } - - #[test] - fn xcm_execute_filter_should_filter_too_deep_xcm() { - //Arrange - let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); - - let max_assets = 2; - let beneficiary = Junction::AccountId32 { - id: [3; 32], - network: None, - } - .into(); - let dest = MultiLocation::new(1, Parachain(2047)); - - let deposit = Xcm(vec![DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }]); - - let mut message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: deposit, - }]); - - for _ in 0..5 { - let xcm = message.clone(); - message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm, - }]); - } - - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - - //Act and assert - assert!(xcm_execute_filter_does_not_allow(&(loc, message))); - } - - #[test] - fn xcm_execute_filter_should_not_filter_message_with_max_deep() { - //Arrange - let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); - - let max_assets = 2; - let beneficiary = Junction::AccountId32 { - id: [3; 32], - network: None, - } - .into(); - let dest = MultiLocation::new(1, Parachain(2047)); - - let deposit = Xcm(vec![DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }]); - - let mut message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: deposit, - }]); - - for _ in 0..4 { - let xcm = message.clone(); - message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm, - }]); - } - - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - - //Act and assert - assert!(AllowTransferAndSwap::, ConstU16<100>, ()>::contains(&( - loc, message - ))); - } - - #[test] - fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() { - //Arrange - let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); - - let max_assets = 2; - let beneficiary = Junction::AccountId32 { - id: [3; 32], - network: None, - } - .into(); - let dest = MultiLocation::new(1, Parachain(2047)); - - let deposit = Xcm(vec![DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }]); - - let mut message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: deposit.clone(), - }]); - - for _ in 0..2 { - let xcm = message.clone(); - message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: xcm.clone(), - }]); - } - - //It has 5 instruction - let mut instructions_with_inner_xcms: Vec> = - vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: message.clone(), - }]; - - let mut rest: Vec> = vec![ - DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }; - 95 - ]; - - instructions_with_inner_xcms.append(&mut rest); - - message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), - dest, - xcm: Xcm(instructions_with_inner_xcms.clone()), - }]); - - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - - //Act and assert - assert!(xcm_execute_filter_does_not_allow(&(loc, message))); - } - - #[test] - fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_one_level() { - //Arrange - let max_assets = 2; - let beneficiary = Junction::AccountId32 { - id: [3; 32], - network: None, - } - .into(); - - let message_with_more_instructions_than_allowed = Xcm(vec![ - DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }; - 101 - ]); - - let loc = MultiLocation::new( - 0, - AccountId32 { - network: None, - id: [1; 32], - }, - ); - - //Act and assert - assert!(xcm_execute_filter_does_not_allow(&( - loc, - message_with_more_instructions_than_allowed - ))); - } - - fn xcm_execute_filter_allows(loc_and_message: &(MultiLocation, Xcm)) -> bool { - AllowTransferAndSwap::, ConstU16<100>, crate::RuntimeCall>::contains(loc_and_message) - } - - fn xcm_execute_filter_does_not_allow(loc_and_message: &(MultiLocation, Xcm<()>)) -> bool { - !AllowTransferAndSwap::, ConstU16<100>, ()>::contains(loc_and_message) - } -} From 3c62c765b15e89fe1c16679f96dea3078b9df41e Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:27:20 +0200 Subject: [PATCH 44/73] rename filter to xcm execute filter --- runtime/adapters/src/lib.rs | 2 +- runtime/adapters/src/tests/mod.rs | 2 +- .../adapters/src/tests/{xcm_filter.rs => xcm_execute_filter.rs} | 2 +- runtime/adapters/src/{filters.rs => xcm_execute_filter.rs} | 0 runtime/hydradx/src/xcm.rs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename runtime/adapters/src/tests/{xcm_filter.rs => xcm_execute_filter.rs} (99%) rename runtime/adapters/src/{filters.rs => xcm_execute_filter.rs} (100%) diff --git a/runtime/adapters/src/lib.rs b/runtime/adapters/src/lib.rs index f001c06ad..25e731e85 100644 --- a/runtime/adapters/src/lib.rs +++ b/runtime/adapters/src/lib.rs @@ -51,9 +51,9 @@ use xcm_executor::{ Assets, }; -pub mod filters; pub mod inspect; pub mod xcm_exchange; +pub mod xcm_execute_filter; #[cfg(test)] mod tests; diff --git a/runtime/adapters/src/tests/mod.rs b/runtime/adapters/src/tests/mod.rs index 57a6b371d..504f68420 100644 --- a/runtime/adapters/src/tests/mod.rs +++ b/runtime/adapters/src/tests/mod.rs @@ -1,4 +1,4 @@ pub mod mock; pub mod trader; pub mod xcm_exchange; -pub mod xcm_filter; +pub mod xcm_execute_filter; diff --git a/runtime/adapters/src/tests/xcm_filter.rs b/runtime/adapters/src/tests/xcm_execute_filter.rs similarity index 99% rename from runtime/adapters/src/tests/xcm_filter.rs rename to runtime/adapters/src/tests/xcm_execute_filter.rs index 68544914a..e6dcc4841 100644 --- a/runtime/adapters/src/tests/xcm_filter.rs +++ b/runtime/adapters/src/tests/xcm_execute_filter.rs @@ -1,5 +1,5 @@ -use crate::filters::AllowTransferAndSwap; use crate::tests::mock::*; +use crate::xcm_execute_filter::AllowTransferAndSwap; use codec::Encode; use frame_support::pallet_prelude::Weight; use frame_support::traits::Contains; diff --git a/runtime/adapters/src/filters.rs b/runtime/adapters/src/xcm_execute_filter.rs similarity index 100% rename from runtime/adapters/src/filters.rs rename to runtime/adapters/src/xcm_execute_filter.rs diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 82558467f..a134ba598 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -27,7 +27,7 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; -use hydradx_adapters::filters::AllowTransferAndSwap; +use hydradx_adapters::xcm_execute_filter::AllowTransferAndSwap; #[derive(Debug, Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct AssetLocation(pub polkadot_xcm::v3::MultiLocation); From 08e98c85718802c461e68daf5b4958a5529d47b1 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:33:42 +0200 Subject: [PATCH 45/73] use sp std instead of normal --- runtime/adapters/src/xcm_execute_filter.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index 9ef4648ac..f8dcca7d8 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -1,4 +1,5 @@ -use core::{cell::Cell, marker::PhantomData}; +use sp_std::cell::Cell; +use sp_std::marker::PhantomData; use frame_support::traits::Contains; use polkadot_xcm::v3::prelude::*; @@ -36,7 +37,7 @@ fn allowed_or_recurse(inst: &Instruction) -> Either( xcm: &Xcm, depth: u16, - instructions: &Cell, //TODO: don't use std, use core or sp_std for Cell + instructions: &Cell, ) -> bool where MaxXcmDepth: Get, From 86e78828da48c2eebd4c9511de777d224e775c61 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:36:01 +0200 Subject: [PATCH 46/73] remove done todo --- runtime/adapters/src/xcm_execute_filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index f8dcca7d8..f16c4fa95 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -46,7 +46,7 @@ where if depth > MaxXcmDepth::get() { return false; } - let instructions_count = instructions; //TODO: use just a let mut u16 + let instructions_count = instructions; let mut iter = xcm.inner().iter(); while let Some(inst) = iter.next() { instructions_count.set(instructions_count.get() + 1); From 09b390b36c718c1a2906a4b802f48bd4202d7c45 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:42:59 +0200 Subject: [PATCH 47/73] add logging for invalid client data for exchange asset --- runtime/adapters/src/xcm_exchange.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 318cf4589..acfe733f6 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -37,13 +37,14 @@ where }; let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way - // TODO: log errors - investigate using defernsive or use log::warn "xcm::exchange-asset" if give.len() != 1 { + log::warn!(target: "xcm::exchange-asset", "Only one give asset is supported."); return Err(give); }; // TODO: create an issue for this as it is easy to have multiple ExchangeAsset, and this would be just then an improvement //We assume only one asset wanted as translating into buy and sell is ambigous for multiple want assets if want.len() != 1 { + log::warn!(target: "xcm::exchange-asset", "Only one want asset is supported."); return Err(give); }; let Some(given) = give.fungible_assets_iter().next() else { From 60eae918b03c984e76a248423d39977ff4cdb7e6 Mon Sep 17 00:00:00 2001 From: dmoka Date: Thu, 13 Jul 2023 20:46:29 +0200 Subject: [PATCH 48/73] ignore origin in exchange asset as it is not relevant for the asset exchange --- runtime/adapters/src/tests/xcm_exchange.rs | 28 +++++++++++++++++++++- runtime/adapters/src/xcm_exchange.rs | 9 ++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/runtime/adapters/src/tests/xcm_exchange.rs b/runtime/adapters/src/tests/xcm_exchange.rs index 71a870497..84a73c902 100644 --- a/runtime/adapters/src/tests/xcm_exchange.rs +++ b/runtime/adapters/src/tests/xcm_exchange.rs @@ -3,7 +3,7 @@ use crate::tests::mock::AssetId as CurrencyId; use crate::tests::mock::*; use crate::tests::mock::{DAI, HDX, NATIVE_AMOUNT}; use crate::xcm_exchange::OmniExchanger; -use frame_support::{assert_noop, parameter_types}; +use frame_support::{assert_noop, assert_ok, parameter_types}; use orml_traits::MultiCurrency; use polkadot_xcm::latest::prelude::*; use pretty_assertions::assert_eq; @@ -142,3 +142,29 @@ fn omni_exchanger_should_not_allow_trading_for_multiple_assets() { ); }); } + +#[test] +fn omni_exchanger_works_with_specified_origin() { + // Arrange + ExtBuilder::default() + .with_endowed_accounts(vec![ + (Omnipool::protocol_account(), DAI, 1000 * ONE), + (Omnipool::protocol_account(), HDX, NATIVE_AMOUNT), + ]) + .with_initial_pool(FixedU128::from_float(0.5), FixedU128::from(1)) + .build() + .execute_with(|| { + let give = MultiAsset::from((GeneralIndex(DAI.into()), 100 * UNITS)).into(); + let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees + let want = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); + // Act + assert_ok!( + OmniExchanger::::exchange_asset( + Some(&MultiLocation::here()), + give, + &want, + SELL, + ) + ); + }); +} diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index acfe733f6..52430789c 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -22,19 +22,14 @@ where T::Balance: From + Zero + Into, { fn exchange_asset( - origin: Option<&MultiLocation>, + _origin: Option<&MultiLocation>, give: xcm_executor::Assets, want: &MultiAssets, maximal: bool, ) -> Result { use orml_utilities::with_transaction_result; - let account = if origin.is_none() { - TempAccount::get() - } else { - // TODO: we want to use temo account alwas becuase there is no sense using specific account for this "accounting/burning/minting/etc" temp work - return Err(give); - }; + let account = TempAccount::get(); let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way if give.len() != 1 { From 8a5fcfdf9dc7e4f410110240392357445e15a6fb Mon Sep 17 00:00:00 2001 From: dmoka Date: Fri, 14 Jul 2023 08:33:25 +0200 Subject: [PATCH 49/73] make xcm asset exchanger generic over pool type with default pool --- runtime/adapters/src/tests/xcm_exchange.rs | 57 +++++++++++----------- runtime/adapters/src/xcm_exchange.rs | 35 ++++++------- runtime/hydradx/src/xcm.rs | 7 ++- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/runtime/adapters/src/tests/xcm_exchange.rs b/runtime/adapters/src/tests/xcm_exchange.rs index 84a73c902..0a5b75a3e 100644 --- a/runtime/adapters/src/tests/xcm_exchange.rs +++ b/runtime/adapters/src/tests/xcm_exchange.rs @@ -2,16 +2,20 @@ use crate::tests::mock::AccountId; use crate::tests::mock::AssetId as CurrencyId; use crate::tests::mock::*; use crate::tests::mock::{DAI, HDX, NATIVE_AMOUNT}; -use crate::xcm_exchange::OmniExchanger; +use crate::xcm_exchange::XcmAssetExchanger; use frame_support::{assert_noop, assert_ok, parameter_types}; +use hydradx_traits::router::PoolType; use orml_traits::MultiCurrency; use polkadot_xcm::latest::prelude::*; use pretty_assertions::assert_eq; use sp_runtime::traits::Convert; use sp_runtime::{FixedU128, SaturatedConversion}; use xcm_executor::traits::AssetExchange; +use xcm_executor::Assets; + parameter_types! { pub ExchangeTempAccount: AccountId = 12345; + pub DefaultPoolType: PoolType = PoolType::Omnipool; } const BUY: bool = false; @@ -58,12 +62,11 @@ fn omni_exchanger_allows_selling_supported_assets() { .execute_with(|| { let give = MultiAsset::from((GeneralIndex(DAI.into()), 100 * UNITS)).into(); let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees - let want = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); + let want: MultiAssets = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); + // Act - let received = OmniExchanger::::exchange_asset( - None, give, &want, SELL, - ) - .expect("should return ok"); + let received = exchange_asset(None, give, &want, SELL).expect("should return ok"); + // Assert let mut iter = received.fungible_assets_iter(); let asset_received = iter.next().expect("there should be at least one asset"); @@ -91,12 +94,11 @@ fn omni_exchanger_allows_buying_supported_assets() { let give = give_asset.into(); let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees let want_asset = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)); - let want = want_asset.clone().into(); + let want: MultiAssets = want_asset.clone().into(); + // Act - let received = OmniExchanger::::exchange_asset( - None, give, &want, BUY, - ) - .expect("should return ok"); + let received = exchange_asset(None, give, &want, BUY).expect("should return ok"); + // Assert let mut iter = received.fungible_assets_iter(); let asset_received = iter.next().expect("there should be at least one asset"); @@ -131,15 +133,7 @@ fn omni_exchanger_should_not_allow_trading_for_multiple_assets() { let want: MultiAssets = vec![want1, want2].into(); // Act and assert - assert_noop!( - OmniExchanger::::exchange_asset( - None, - give.clone().into(), - &want, - SELL - ), - give - ); + assert_noop!(exchange_asset(None, give.clone().into(), &want, SELL), give); }); } @@ -157,14 +151,19 @@ fn omni_exchanger_works_with_specified_origin() { let give = MultiAsset::from((GeneralIndex(DAI.into()), 100 * UNITS)).into(); let wanted_amount = 45 * UNITS; // 50 - 5 to cover fees let want = MultiAsset::from((GeneralIndex(HDX.into()), wanted_amount)).into(); - // Act - assert_ok!( - OmniExchanger::::exchange_asset( - Some(&MultiLocation::here()), - give, - &want, - SELL, - ) - ); + + // Act and assert + assert_ok!(exchange_asset(Some(&MultiLocation::here()), give, &want, SELL)); }); } + +fn exchange_asset( + origin: Option<&MultiLocation>, + give: Assets, + want: &MultiAssets, + is_sell: bool, +) -> Result { + XcmAssetExchanger::::exchange_asset( + origin, give, want, is_sell, + ) +} diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 52430789c..6a1ba7a76 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -1,4 +1,4 @@ -use hydradx_traits::router::PoolType::Omnipool; +use hydradx_traits::router::PoolType; use orml_traits::MultiCurrency; use pallet_route_executor::Trade; use polkadot_xcm::latest::prelude::*; @@ -8,18 +8,19 @@ use sp_std::marker::PhantomData; use sp_std::vec; use xcm_executor::traits::AssetExchange; -pub struct OmniExchanger( - PhantomData<(T, TempAccount, CurrencyIdConvert, Currency)>, +pub struct XcmAssetExchanger( + PhantomData<(Runtime, TempAccount, CurrencyIdConvert, Currency, DefaultPoolType)>, ); -impl AssetExchange - for OmniExchanger +impl AssetExchange + for XcmAssetExchanger where - T: pallet_route_executor::Config, - TempAccount: Get, - CurrencyIdConvert: Convert>, - Currency: MultiCurrency, - T::Balance: From + Zero + Into, + Runtime: pallet_route_executor::Config, + TempAccount: Get, + CurrencyIdConvert: Convert>, + Currency: MultiCurrency, + Runtime::Balance: From + Zero + Into, + DefaultPoolType: Get>, { fn exchange_asset( _origin: Option<&MultiLocation>, @@ -30,7 +31,7 @@ where use orml_utilities::with_transaction_result; let account = TempAccount::get(); - let origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + let origin = Runtime::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way if give.len() != 1 { log::warn!(target: "xcm::exchange-asset", "Only one give asset is supported."); @@ -57,20 +58,20 @@ where with_transaction_result(|| { Currency::deposit(asset_in, &account, amount.into())?; // mint the incoming tokens - pallet_route_executor::Pallet::::sell( + pallet_route_executor::Pallet::::sell( origin, asset_in, asset_out, amount.into(), min_buy_amount.into(), vec![Trade { - pool: Omnipool, + pool: DefaultPoolType::get(), asset_in, asset_out, }], )?; debug_assert!( - Currency::free_balance(asset_in, &account) == T::Balance::zero(), + Currency::free_balance(asset_in, &account) == Runtime::Balance::zero(), "Sell should not leave any of the incoming asset." ); let amount_received = Currency::free_balance(asset_out, &account); @@ -89,21 +90,21 @@ where with_transaction_result(|| { Currency::deposit(asset_in, &account, max_sell_amount.into())?; // mint the incoming tokens - pallet_route_executor::Pallet::::buy( + pallet_route_executor::Pallet::::buy( origin, asset_in, asset_out, amount.into(), max_sell_amount.into(), vec![Trade { - pool: Omnipool, + pool: DefaultPoolType::get(), asset_in, asset_out, }], )?; let mut assets = sp_std::vec::Vec::new(); let left_over = Currency::free_balance(asset_in, &account); - if left_over > T::Balance::zero() { + if left_over > Runtime::Balance::zero() { Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens assets.push(MultiAsset::from((given.id, left_over.into()))); } diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index a134ba598..e0f4a6c87 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -13,6 +13,7 @@ use frame_support::{ PalletId, }; use frame_system::EnsureRoot; +use hydradx_traits::router::PoolType; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; @@ -82,6 +83,8 @@ parameter_types! { pub const MaxInstructions: u32 = 100; pub const MaxAssetsForTransfer: usize = 2; + pub DefaultPoolType: PoolType = PoolType::Omnipool; + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); pub TempAccount: AccountId = [42; 32].into(); } @@ -116,7 +119,7 @@ impl Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetLocker = (); - type AssetExchanger = OmniExchanger; + type AssetExchanger = XcmAssetExchanger; type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; @@ -220,7 +223,7 @@ impl pallet_xcm::Config for Runtime { } pub struct CurrencyIdConvert; -use hydradx_adapters::xcm_exchange::OmniExchanger; +use hydradx_adapters::xcm_exchange::XcmAssetExchanger; use primitives::constants::chain::CORE_ASSET_ID; impl Convert> for CurrencyIdConvert { From 2dc5f5bfc1d595464754809dc5c5c8242f7915be Mon Sep 17 00:00:00 2001 From: dmoka Date: Fri, 14 Jul 2023 08:35:09 +0200 Subject: [PATCH 50/73] remove todos --- runtime/adapters/src/xcm_exchange.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 6a1ba7a76..856a69b03 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -31,12 +31,12 @@ where use orml_utilities::with_transaction_result; let account = TempAccount::get(); - let origin = Runtime::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); //TODO: check how else it is done in hydra in a simpler way + let origin = Runtime::RuntimeOrigin::from(frame_system::RawOrigin::Signed(account.clone())); if give.len() != 1 { log::warn!(target: "xcm::exchange-asset", "Only one give asset is supported."); return Err(give); - }; // TODO: create an issue for this as it is easy to have multiple ExchangeAsset, and this would be just then an improvement + }; //We assume only one asset wanted as translating into buy and sell is ambigous for multiple want assets if want.len() != 1 { From 3d9d6fe39f98c10bc49de7c7df25e3a82f0d0114 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 16:19:09 +0200 Subject: [PATCH 51/73] WIP - add test for transfer and swap --- integration-tests/src/exchange_asset.rs | 340 ++++++++++++++++++++- integration-tests/src/polkadot_test_net.rs | 30 +- 2 files changed, 365 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 3871242f6..b794795f6 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -1,19 +1,175 @@ #![cfg(test)] use crate::polkadot_test_net::*; +use frame_support::dispatch::GetDispatchInfo; +use frame_support::traits::fungible::Balanced; use frame_support::weights::Weight; use frame_support::{assert_ok, pallet_prelude::*}; use orml_traits::currency::MultiCurrency; use polkadot_xcm::{latest::prelude::*, VersionedXcm}; use pretty_assertions::assert_eq; +use sp_runtime::traits::{Convert, Zero}; use sp_runtime::{FixedU128, Permill}; use xcm_emulator::TestExt; -use frame_support::dispatch::GetDispatchInfo; - pub const SELL: bool = true; pub const BUY: bool = false; +fn craft_exchange_asset_xcm2( + give_asset: MultiAsset, + want_asset: MultiAsset, + is_sell: bool, +) -> VersionedXcm { + use polkadot_runtime::xcm_config::BaseXcmWeight; + use xcm_builder::FixedWeightBounds; + use xcm_executor::traits::WeightBounds; + + type Weigher = FixedWeightBounds>; + + let give_reserve_chain = MultiLocation::new(1, Parachain(MOONBEAM_PARA_ID)); + let want_reserve_chain = MultiLocation::new(1, Parachain(INTERLAY_PARA_ID)); + let swap_chain = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); + let dest = MultiLocation::new(1, Parachain(ACALA_PARA_ID)); + let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); + let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded + let max_assets = assets.len() as u32 + 1; + let origin_context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); + let fees = assets + .get(0) + .expect("should have at least 1 asset") + .clone() + .reanchored(&dest, origin_context) + .expect("should reanchor"); + let give = give_asset + .clone() + .reanchored(&dest, origin_context) + .expect("should reanchor give"); + let give: MultiAssetFilter = Definite(give.clone().into()); + let want: MultiAssets = want_asset.clone().into(); + + let fees = give_asset + .clone() + .reanchored(&swap_chain, give_reserve_chain.interior) + .expect("should reanchor"); + + let reserve_fees = want_asset + .clone() + .reanchored(&want_reserve_chain, swap_chain.interior) + .expect("should reanchor"); + + let destination_fee = want_asset + .clone() + .reanchored(&dest, want_reserve_chain.interior) + .expect("should reanchor"); + + let weight_limit = { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited::(assets.clone()), + ClearOrigin, + BuyExecution { + fees: fees.clone(), + weight_limit: Limited(Weight::zero()), + }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: is_sell, + }, + InitiateReserveWithdraw { + assets: want.clone().into(), + reserve: want_reserve_chain, + xcm: Xcm(vec![ + BuyExecution { + fees: reserve_fees.clone(), //reserve fee + weight_limit: Limited(Weight::zero()), + }, + DepositReserveAsset { + assets: Wild(AllCounted(max_assets)), + dest, + xcm: Xcm(vec![ + BuyExecution { + fees: destination_fee.clone(), //destination fee + weight_limit: Limited(Weight::zero()), + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + 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 remote (on hydra) + let xcm = Xcm(vec![ + BuyExecution { + fees: half(&fees), + weight_limit: weight_limit.clone(), + }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: is_sell, + }, + InitiateReserveWithdraw { + assets: want.into(), + reserve: want_reserve_chain, + xcm: Xcm(vec![ + BuyExecution { + fees: half(&reserve_fees), + weight_limit: weight_limit.clone(), + }, + DepositReserveAsset { + assets: Wild(AllCounted(max_assets)), + dest, + xcm: Xcm(vec![ + BuyExecution { + fees: half(&destination_fee), + weight_limit: weight_limit.clone(), + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]), + }, + ]), + }, + ]); + + let give_reserve_fees = give_asset + .clone() + .reanchored(&give_reserve_chain, origin_context) + .expect("should reanchor"); + + // executed on local (acala) + let message = Xcm(vec![ + WithdrawAsset(give_asset.clone().into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: give_reserve_chain, + xcm: Xcm(vec![ + BuyExecution { + fees: half(&give_reserve_fees), + weight_limit: weight_limit.clone(), + }, + DepositReserveAsset { + assets: AllCounted(max_assets).into(), + dest, + xcm, + }, + ]), + }, + ]); + VersionedXcm::V3(message) +} + fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( give: MultiAsset, want: M, @@ -251,6 +407,186 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { }); } +#[test] +fn hydra_should_transfer_and_swap_send_back_to_acala() { + //Arrange + TestNet::reset(); + + let moon = 4567; //TODO: RENAME TO glmr + let btc = 7890; + Hydra::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"MOON".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(moon), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(MOONBEAM_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"iBTC".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(btc), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(INTERLAY_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( + hydradx_runtime::RuntimeOrigin::root(), + moon, + FixedU128::from(1), + )); + + init_omnipool(); + let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); + + let token_price = FixedU128::from_float(1.0); + assert_ok!(hydradx_runtime::Tokens::deposit(moon, &omnipool_account, 3000 * UNITS)); + assert_ok!(hydradx_runtime::Tokens::deposit(btc, &omnipool_account, 3000 * UNITS)); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + moon, + token_price, + Permill::from_percent(100), + AccountId::from(BOB), + )); + + assert_ok!(hydradx_runtime::Omnipool::add_token( + hydradx_runtime::RuntimeOrigin::root(), + btc, + token_price, + Permill::from_percent(100), + AccountId::from(BOB), + )); + }); + + Moonbeam::execute_with(|| { + use xcm_executor::traits::Convert; + let para_account = + hydradx_runtime::LocationToAccountId::convert((Parent, Parachain(ACALA_PARA_ID)).into()).unwrap(); + hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); + }); + + Interlay::execute_with(|| { + use xcm_executor::traits::Convert; + let para_account = + hydradx_runtime::LocationToAccountId::convert((Parent, Parachain(HYDRA_PARA_ID)).into()).unwrap(); + hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); + }); + + Acala::execute_with(|| { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"MOON".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(moon), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(MOONBEAM_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"iBTC".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(btc), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(INTERLAY_PARA_ID), GeneralIndex(0)) + ))), + None + )); + + assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( + hydradx_runtime::RuntimeOrigin::root(), + btc, + FixedU128::from(1), + )); + + let alice_init_moon_balance = 3000 * UNITS; + assert_ok!(hydradx_runtime::Tokens::deposit( + moon, + &ALICE.into(), + alice_init_moon_balance + )); + + //Act + + let give_amount = 1000 * UNITS; + let give = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(moon).unwrap(), give_amount)); + let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 600 * UNITS)); + + let xcm = craft_exchange_asset_xcm2::(give, want, SELL); + assert_ok!(hydradx_runtime::PolkadotXcm::execute( + hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), + Box::new(xcm), + Weight::from_ref_time(399_600_000_000), + )); + + //Assert + assert_eq!( + hydradx_runtime::Tokens::free_balance(moon, &AccountId::from(ALICE)), + alice_init_moon_balance - give_amount + ); + // TODO: add utility macro? + /*assert!(matches!( + last_hydra_events(2).first(), + Some(hydradx_runtime::RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + )) + ));*/ + }); + + //let fees = 500801282051; + Acala::execute_with(|| { + assert_eq!( + hydradx_runtime::Currencies::free_balance(btc, &AccountId::from(BOB)), + 10 * UNITS + ); + /*assert_eq!( + hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), + fees + );*/ + }); +} +/// Returns amount if `asset` is fungible, or zero. +fn fungible_amount(asset: &MultiAsset) -> u128 { + if let Fungible(amount) = &asset.fun { + *amount + } else { + Zero::zero() + } +} + +fn half(asset: &MultiAsset) -> MultiAsset { + let half_amount = fungible_amount(asset) + .checked_div(2) + .expect("div 2 can't overflow; qed"); + MultiAsset { + fun: Fungible(half_amount), + id: asset.id, + } +} + // TODO test with Acala -> Hydra Swap -> Acala // TODO: we want to make sure that the different combinations work // TODO: implement the most complex version: 4hops, 5 chains involved diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index 07f969462..eb226b2f1 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -29,6 +29,8 @@ pub const UNITS: Balance = 1_000_000_000_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; +pub const INTERLAY_PARA_ID: u32 = 2_032; pub const ALICE_INITIAL_NATIVE_BALANCE: Balance = 1_000 * UNITS; pub const ALICE_INITIAL_DAI_BALANCE: Balance = 200 * UNITS; @@ -72,7 +74,27 @@ decl_test_parachain! { RuntimeOrigin = hydradx_runtime::RuntimeOrigin, XcmpMessageHandler = hydradx_runtime::XcmpQueue, DmpMessageHandler = hydradx_runtime::DmpQueue, - new_ext = acala_ext(), + new_ext = para_ext(ACALA_PARA_ID), + } +} + +decl_test_parachain! { + pub struct Moonbeam{ + Runtime = hydradx_runtime::Runtime, + RuntimeOrigin = hydradx_runtime::RuntimeOrigin, + XcmpMessageHandler = hydradx_runtime::XcmpQueue, + DmpMessageHandler = hydradx_runtime::DmpQueue, + new_ext = para_ext(MOONBEAM_PARA_ID), + } +} + +decl_test_parachain! { + pub struct Interlay { + Runtime = hydradx_runtime::Runtime, + RuntimeOrigin = hydradx_runtime::RuntimeOrigin, + XcmpMessageHandler = hydradx_runtime::XcmpQueue, + DmpMessageHandler = hydradx_runtime::DmpQueue, + new_ext = para_ext(INTERLAY_PARA_ID), } } @@ -81,6 +103,8 @@ decl_test_network! { relay_chain = PolkadotRelay, parachains = vec![ (2000, Acala), + (2004, Moonbeam), + (2032, Interlay), (2034, Hydra), ], } @@ -274,7 +298,7 @@ pub fn hydra_ext() -> sp_io::TestExternalities { ext } -pub fn acala_ext() -> sp_io::TestExternalities { +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { use hydradx_runtime::{Runtime, System}; let mut t = frame_system::GenesisConfig::default() @@ -289,7 +313,7 @@ pub fn acala_ext() -> sp_io::TestExternalities { >::assimilate_storage( ¶chain_info::GenesisConfig { - parachain_id: ACALA_PARA_ID.into(), + parachain_id: para_id.into(), }, &mut t, ) From aecd4d898b1bc161109f535c84ebbc07031dae9f Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 17 Jul 2023 17:39:35 +0200 Subject: [PATCH 52/73] fix integration test --- integration-tests/src/exchange_asset.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index b794795f6..569c1ab14 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -34,12 +34,6 @@ fn craft_exchange_asset_xcm2( let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32 + 1; let origin_context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); - let fees = assets - .get(0) - .expect("should have at least 1 asset") - .clone() - .reanchored(&dest, origin_context) - .expect("should reanchor"); let give = give_asset .clone() .reanchored(&dest, origin_context) @@ -161,7 +155,7 @@ fn craft_exchange_asset_xcm2( }, DepositReserveAsset { assets: AllCounted(max_assets).into(), - dest, + dest: swap_chain, xcm, }, ]), @@ -448,6 +442,11 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { moon, FixedU128::from(1), )); + use hydradx_traits::NativePriceOracle; + // assert_eq!(hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), FixedU128::from(1)); + // make sure the price is propagated + hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); + assert_eq!(hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), FixedU128::from(1)); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); @@ -521,6 +520,8 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { btc, FixedU128::from(1), )); + // make sure the price is propagated + hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); let alice_init_moon_balance = 3000 * UNITS; assert_ok!(hydradx_runtime::Tokens::deposit( @@ -533,7 +534,7 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { let give_amount = 1000 * UNITS; let give = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(moon).unwrap(), give_amount)); - let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 600 * UNITS)); + let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 550 * UNITS)); let xcm = craft_exchange_asset_xcm2::(give, want, SELL); assert_ok!(hydradx_runtime::PolkadotXcm::execute( @@ -560,7 +561,7 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { Acala::execute_with(|| { assert_eq!( hydradx_runtime::Currencies::free_balance(btc, &AccountId::from(BOB)), - 10 * UNITS + 549198717948718 ); /*assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), From a3b1da7c5d06ad18c3146ea3c0cc6f1df88344e2 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 17 Jul 2023 17:39:35 +0200 Subject: [PATCH 53/73] fix integration test --- integration-tests/src/exchange_asset.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index b794795f6..0866114bf 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -34,12 +34,6 @@ fn craft_exchange_asset_xcm2( let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded let max_assets = assets.len() as u32 + 1; let origin_context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); - let fees = assets - .get(0) - .expect("should have at least 1 asset") - .clone() - .reanchored(&dest, origin_context) - .expect("should reanchor"); let give = give_asset .clone() .reanchored(&dest, origin_context) @@ -161,7 +155,7 @@ fn craft_exchange_asset_xcm2( }, DepositReserveAsset { assets: AllCounted(max_assets).into(), - dest, + dest: swap_chain, xcm, }, ]), @@ -448,6 +442,14 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { moon, FixedU128::from(1), )); + use hydradx_traits::NativePriceOracle; + // assert_eq!(hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), FixedU128::from(1)); + // make sure the price is propagated + hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); + assert_eq!( + hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), + FixedU128::from(1) + ); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); @@ -521,6 +523,8 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { btc, FixedU128::from(1), )); + // make sure the price is propagated + hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); let alice_init_moon_balance = 3000 * UNITS; assert_ok!(hydradx_runtime::Tokens::deposit( @@ -533,7 +537,7 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { let give_amount = 1000 * UNITS; let give = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(moon).unwrap(), give_amount)); - let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 600 * UNITS)); + let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 550 * UNITS)); let xcm = craft_exchange_asset_xcm2::(give, want, SELL); assert_ok!(hydradx_runtime::PolkadotXcm::execute( @@ -560,7 +564,7 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { Acala::execute_with(|| { assert_eq!( hydradx_runtime::Currencies::free_balance(btc, &AccountId::from(BOB)), - 10 * UNITS + 549198717948718 ); /*assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), From fab79ab668f9eeacfc6b173e2f182dc4bd8fd94d Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Mon, 17 Jul 2023 17:53:27 +0200 Subject: [PATCH 54/73] improve test clarity --- integration-tests/src/exchange_asset.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 0866114bf..ea5e3debc 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -306,7 +306,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), 50 * UNITS - fees ); - // We receive about 39_101 HDX + // We receive about 39_101 HDX (HDX is super cheap in our test) let received = 39_101 * UNITS + BOB_INITIAL_NATIVE_BALANCE + 207_131_554_396; assert_eq!(hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), received); assert_eq!( @@ -316,7 +316,6 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { }); } -//TODO: double check if this buy make sense, especially in the end, bob's aca balanced changed more than the fee #[test] fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { //Arrange @@ -384,11 +383,12 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { )); }); - let fees = 862495197993; + let fees = 500801282051; + let swapped = 361693915942; // HDX is super cheap in our setup Hydra::execute_with(|| { assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), - 100 * UNITS - fees + 100 * UNITS - swapped - fees ); assert_eq!( hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), @@ -396,7 +396,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { ); assert_eq!( hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), - 500801282051 + fees ); }); } From ca0c934d0320bddd2030c71e5da0e0b7a1ee2ea0 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 17:59:23 +0200 Subject: [PATCH 55/73] add helper comments on the execution chain --- integration-tests/src/exchange_asset.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index ea5e3debc..a21c1819b 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -115,6 +115,7 @@ fn craft_exchange_asset_xcm2( assets: want.into(), reserve: want_reserve_chain, xcm: Xcm(vec![ + //Executed on interlay BuyExecution { fees: half(&reserve_fees), weight_limit: weight_limit.clone(), @@ -123,6 +124,7 @@ fn craft_exchange_asset_xcm2( assets: Wild(AllCounted(max_assets)), dest, xcm: Xcm(vec![ + //Executed on acala BuyExecution { fees: half(&destination_fee), weight_limit: weight_limit.clone(), @@ -149,6 +151,7 @@ fn craft_exchange_asset_xcm2( assets: All.into(), reserve: give_reserve_chain, xcm: Xcm(vec![ + //Executed on moonbeam BuyExecution { fees: half(&give_reserve_fees), weight_limit: weight_limit.clone(), From 7b0d395f2ff15b8946d0681fb582f18cf8fbd2e2 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:13:21 +0200 Subject: [PATCH 56/73] clean up tests --- integration-tests/src/exchange_asset.rs | 626 +++++++++++------------- 1 file changed, 286 insertions(+), 340 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index a21c1819b..d5e33c99d 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -15,226 +15,8 @@ use xcm_emulator::TestExt; pub const SELL: bool = true; pub const BUY: bool = false; -fn craft_exchange_asset_xcm2( - give_asset: MultiAsset, - want_asset: MultiAsset, - is_sell: bool, -) -> VersionedXcm { - use polkadot_runtime::xcm_config::BaseXcmWeight; - use xcm_builder::FixedWeightBounds; - use xcm_executor::traits::WeightBounds; - - type Weigher = FixedWeightBounds>; - - let give_reserve_chain = MultiLocation::new(1, Parachain(MOONBEAM_PARA_ID)); - let want_reserve_chain = MultiLocation::new(1, Parachain(INTERLAY_PARA_ID)); - let swap_chain = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); - let dest = MultiLocation::new(1, Parachain(ACALA_PARA_ID)); - let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); - let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded - let max_assets = assets.len() as u32 + 1; - let origin_context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); - let give = give_asset - .clone() - .reanchored(&dest, origin_context) - .expect("should reanchor give"); - let give: MultiAssetFilter = Definite(give.clone().into()); - let want: MultiAssets = want_asset.clone().into(); - - let fees = give_asset - .clone() - .reanchored(&swap_chain, give_reserve_chain.interior) - .expect("should reanchor"); - - let reserve_fees = want_asset - .clone() - .reanchored(&want_reserve_chain, swap_chain.interior) - .expect("should reanchor"); - - let destination_fee = want_asset - .clone() - .reanchored(&dest, want_reserve_chain.interior) - .expect("should reanchor"); - - let weight_limit = { - let fees = fees.clone(); - let mut remote_message = Xcm(vec![ - ReserveAssetDeposited::(assets.clone()), - ClearOrigin, - BuyExecution { - fees: fees.clone(), - weight_limit: Limited(Weight::zero()), - }, - ExchangeAsset { - give: give.clone(), - want: want.clone(), - maximal: is_sell, - }, - InitiateReserveWithdraw { - assets: want.clone().into(), - reserve: want_reserve_chain, - xcm: Xcm(vec![ - BuyExecution { - fees: reserve_fees.clone(), //reserve fee - weight_limit: Limited(Weight::zero()), - }, - DepositReserveAsset { - assets: Wild(AllCounted(max_assets)), - dest, - xcm: Xcm(vec![ - BuyExecution { - fees: destination_fee.clone(), //destination fee - weight_limit: Limited(Weight::zero()), - }, - DepositAsset { - assets: Wild(AllCounted(max_assets)), - 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 remote (on hydra) - let xcm = Xcm(vec![ - BuyExecution { - fees: half(&fees), - weight_limit: weight_limit.clone(), - }, - ExchangeAsset { - give: give.clone(), - want: want.clone(), - maximal: is_sell, - }, - InitiateReserveWithdraw { - assets: want.into(), - reserve: want_reserve_chain, - xcm: Xcm(vec![ - //Executed on interlay - BuyExecution { - fees: half(&reserve_fees), - weight_limit: weight_limit.clone(), - }, - DepositReserveAsset { - assets: Wild(AllCounted(max_assets)), - dest, - xcm: Xcm(vec![ - //Executed on acala - BuyExecution { - fees: half(&destination_fee), - weight_limit: weight_limit.clone(), - }, - DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }, - ]), - }, - ]), - }, - ]); - - let give_reserve_fees = give_asset - .clone() - .reanchored(&give_reserve_chain, origin_context) - .expect("should reanchor"); - - // executed on local (acala) - let message = Xcm(vec![ - WithdrawAsset(give_asset.clone().into()), - InitiateReserveWithdraw { - assets: All.into(), - reserve: give_reserve_chain, - xcm: Xcm(vec![ - //Executed on moonbeam - BuyExecution { - fees: half(&give_reserve_fees), - weight_limit: weight_limit.clone(), - }, - DepositReserveAsset { - assets: AllCounted(max_assets).into(), - dest: swap_chain, - xcm, - }, - ]), - }, - ]); - VersionedXcm::V3(message) -} - -fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( - give: MultiAsset, - want: M, - is_sell: bool, -) -> 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 beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); - let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded - let max_assets = assets.len() as u32 + 1; - let context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); - let fees = assets - .get(0) - .expect("should have at least 1 asset") - .clone() - .reanchored(&dest, context) - .expect("should reanchor"); - let give = give.reanchored(&dest, context).expect("should reanchor give"); - let give: MultiAssetFilter = Definite(give.into()); - let want = want.into(); - let weight_limit = { - let fees = fees.clone(); - let mut remote_message = Xcm(vec![ - ReserveAssetDeposited::(assets.clone()), - ClearOrigin, - BuyExecution { - fees, - weight_limit: Limited(Weight::zero()), - }, - ExchangeAsset { - give: give.clone(), - want: want.clone(), - maximal: is_sell, - }, - DepositAsset { - assets: Wild(AllCounted(max_assets)), - 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 remote (on hydra) - let xcm = Xcm(vec![ - BuyExecution { fees, weight_limit }, - ExchangeAsset { - give, - want, - maximal: is_sell, - }, - DepositAsset { - assets: Wild(AllCounted(max_assets)), - beneficiary, - }, - ]); - // executed on local (acala) - let message = Xcm(vec![ - SetFeesMode { jit_withdraw: true }, - TransferReserveAsset { assets, dest, xcm }, - ]); - VersionedXcm::V3(message) -} +pub const GLMR: u32 = 4567; +pub const IBTC: u32 = 7890; #[test] fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { @@ -294,7 +76,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), ALICE_INITIAL_NATIVE_BALANCE - 100 * UNITS ); - // TODO: add utility macro? + assert!(matches!( last_hydra_events(2).first(), Some(hydradx_runtime::RuntimeEvent::XcmpQueue( @@ -377,7 +159,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { hydradx_runtime::Balances::free_balance(AccountId::from(ALICE)), ALICE_INITIAL_NATIVE_BALANCE - 100 * UNITS ); - // TODO: add utility macro? + assert!(matches!( last_hydra_events(2).first(), Some(hydradx_runtime::RuntimeEvent::XcmpQueue( @@ -404,66 +186,35 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { }); } +//We swap GLMR for iBTC, sent from ACALA and executed on Hydradx, resultin in 4 hops #[test] -fn hydra_should_transfer_and_swap_send_back_to_acala() { +fn transfer_and_swap_should_work_with_4_hops() { //Arrange TestNet::reset(); - let moon = 4567; //TODO: RENAME TO glmr - let btc = 7890; Hydra::execute_with(|| { - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"MOON".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(moon), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(MOONBEAM_PARA_ID), GeneralIndex(0)) - ))), - None - )); - - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"iBTC".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(btc), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(INTERLAY_PARA_ID), GeneralIndex(0)) - ))), - None - )); + register_glmr(); + register_ibtc(); assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( hydradx_runtime::RuntimeOrigin::root(), - moon, + GLMR, FixedU128::from(1), )); - use hydradx_traits::NativePriceOracle; - // assert_eq!(hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), FixedU128::from(1)); + // make sure the price is propagated hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); - assert_eq!( - hydradx_runtime::MultiTransactionPayment::price(moon).unwrap(), - FixedU128::from(1) - ); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); let token_price = FixedU128::from_float(1.0); - assert_ok!(hydradx_runtime::Tokens::deposit(moon, &omnipool_account, 3000 * UNITS)); - assert_ok!(hydradx_runtime::Tokens::deposit(btc, &omnipool_account, 3000 * UNITS)); + assert_ok!(hydradx_runtime::Tokens::deposit(GLMR, &omnipool_account, 3000 * UNITS)); + assert_ok!(hydradx_runtime::Tokens::deposit(IBTC, &omnipool_account, 3000 * UNITS)); assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - moon, + GLMR, token_price, Permill::from_percent(100), AccountId::from(BOB), @@ -471,7 +222,7 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - btc, + IBTC, token_price, Permill::from_percent(100), AccountId::from(BOB), @@ -482,67 +233,42 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { use xcm_executor::traits::Convert; let para_account = hydradx_runtime::LocationToAccountId::convert((Parent, Parachain(ACALA_PARA_ID)).into()).unwrap(); - hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); + let _ = hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); }); Interlay::execute_with(|| { use xcm_executor::traits::Convert; let para_account = hydradx_runtime::LocationToAccountId::convert((Parent, Parachain(HYDRA_PARA_ID)).into()).unwrap(); - hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); + let _ = hydradx_runtime::Balances::deposit(¶_account, 1000 * UNITS).expect("Failed to deposit"); }); Acala::execute_with(|| { - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"MOON".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(moon), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(MOONBEAM_PARA_ID), GeneralIndex(0)) - ))), - None - )); - - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"iBTC".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(btc), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(INTERLAY_PARA_ID), GeneralIndex(0)) - ))), - None - )); + register_glmr(); + register_ibtc(); assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( hydradx_runtime::RuntimeOrigin::root(), - btc, + IBTC, FixedU128::from(1), )); + // make sure the price is propagated hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); let alice_init_moon_balance = 3000 * UNITS; assert_ok!(hydradx_runtime::Tokens::deposit( - moon, + GLMR, &ALICE.into(), alice_init_moon_balance )); //Act - let give_amount = 1000 * UNITS; - let give = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(moon).unwrap(), give_amount)); - let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(btc).unwrap(), 550 * UNITS)); + let give = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(GLMR).unwrap(), give_amount)); + let want = MultiAsset::from((hydradx_runtime::CurrencyIdConvert::convert(IBTC).unwrap(), 550 * UNITS)); - let xcm = craft_exchange_asset_xcm2::(give, want, SELL); + let xcm = craft_transfer_and_swap_xcm_with_4_hops::(give, want, SELL); assert_ok!(hydradx_runtime::PolkadotXcm::execute( hydradx_runtime::RuntimeOrigin::signed(ALICE.into()), Box::new(xcm), @@ -551,30 +277,63 @@ fn hydra_should_transfer_and_swap_send_back_to_acala() { //Assert assert_eq!( - hydradx_runtime::Tokens::free_balance(moon, &AccountId::from(ALICE)), + hydradx_runtime::Tokens::free_balance(GLMR, &AccountId::from(ALICE)), alice_init_moon_balance - give_amount ); - // TODO: add utility macro? - /*assert!(matches!( + + assert!(matches!( last_hydra_events(2).first(), Some(hydradx_runtime::RuntimeEvent::XcmpQueue( cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } )) - ));*/ + )); }); - //let fees = 500801282051; + let fees = 400641025641; Acala::execute_with(|| { assert_eq!( - hydradx_runtime::Currencies::free_balance(btc, &AccountId::from(BOB)), + hydradx_runtime::Currencies::free_balance(IBTC, &AccountId::from(BOB)), 549198717948718 ); - /*assert_eq!( - hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), + assert_eq!( + hydradx_runtime::Tokens::free_balance(IBTC, &hydradx_runtime::Treasury::account_id()), fees - );*/ + ); }); } + +fn register_glmr() { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"GLRM".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(GLMR), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(MOONBEAM_PARA_ID), GeneralIndex(0)) + ))), + None + )); +} + +fn register_ibtc() { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"iBTC".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(IBTC), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(INTERLAY_PARA_ID), GeneralIndex(0)) + ))), + None + )); +} + /// Returns amount if `asset` is fungible, or zero. fn fungible_amount(asset: &MultiAsset) -> u128 { if let Fungible(amount) = &asset.fun { @@ -594,36 +353,223 @@ fn half(asset: &MultiAsset) -> MultiAsset { } } -// TODO test with Acala -> Hydra Swap -> Acala -// TODO: we want to make sure that the different combinations work -// TODO: implement the most complex version: 4hops, 5 chains involved - -// Support different transfers of swap results -// send HDX back to Acala -// DepositReserveAsset { assets: hdx_filter, dest: acala, xcm: -// Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { -// assets: Wild(AllCounted(max_assets)), -// beneficiary, -// }]) -// }, -// send ACA back to Acala -// InitiateReserveWithdraw { assets: aca_filter, reserve: acala, xcm: -// Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { -// assets: Wild(AllCounted(max_assets)), -// beneficiary, -// }]) -// }, -// send BTC back to Acala -// InitiateReserveWithdraw { assets: btc_filter, reserve: interlay, xcm: // Hydra -// Xcm(vec![BuyExecution { fees, weight_limit }, DepositReserveAsset { // Interlay -// assets: Wild(AllCounted(max_assets)), -// dest: acala, -// xcm: Xcm(vec![BuyExecution { fees, weight_limit }, DepositAsset { // Acala -// assets: Wild(AllCounted(max_assets)), -// beneficiary, -// }]), -// }]) -// }, -// send HDX to interlay -// send BTC to interlay -// send ACA to interlay +fn craft_transfer_and_swap_xcm_with_4_hops( + give_asset: MultiAsset, + want_asset: MultiAsset, + is_sell: bool, +) -> VersionedXcm { + use polkadot_runtime::xcm_config::BaseXcmWeight; + use xcm_builder::FixedWeightBounds; + use xcm_executor::traits::WeightBounds; + + type Weigher = FixedWeightBounds>; + + let give_reserve_chain = MultiLocation::new(1, Parachain(MOONBEAM_PARA_ID)); + let want_reserve_chain = MultiLocation::new(1, Parachain(INTERLAY_PARA_ID)); + let swap_chain = MultiLocation::new(1, Parachain(HYDRA_PARA_ID)); + let dest = MultiLocation::new(1, Parachain(ACALA_PARA_ID)); + let beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); + let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded + let max_assets = assets.len() as u32 + 1; + let origin_context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); + let give = give_asset + .clone() + .reanchored(&dest, origin_context) + .expect("should reanchor give"); + let give: MultiAssetFilter = Definite(give.clone().into()); + let want: MultiAssets = want_asset.clone().into(); + + let fees = give_asset + .clone() + .reanchored(&swap_chain, give_reserve_chain.interior) + .expect("should reanchor"); + + let reserve_fees = want_asset + .clone() + .reanchored(&want_reserve_chain, swap_chain.interior) + .expect("should reanchor"); + + let destination_fee = want_asset + .clone() + .reanchored(&dest, want_reserve_chain.interior) + .expect("should reanchor"); + + let weight_limit = { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited::(assets.clone()), + ClearOrigin, + BuyExecution { + fees: fees.clone(), + weight_limit: Limited(Weight::zero()), + }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: is_sell, + }, + InitiateReserveWithdraw { + assets: want.clone().into(), + reserve: want_reserve_chain, + xcm: Xcm(vec![ + BuyExecution { + fees: reserve_fees.clone(), //reserve fee + weight_limit: Limited(Weight::zero()), + }, + DepositReserveAsset { + assets: Wild(AllCounted(max_assets)), + dest, + xcm: Xcm(vec![ + BuyExecution { + fees: destination_fee.clone(), //destination fee + weight_limit: Limited(Weight::zero()), + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + 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 remote (on hydra) + let xcm = Xcm(vec![ + BuyExecution { + fees: half(&fees), + weight_limit: weight_limit.clone(), + }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: is_sell, + }, + InitiateReserveWithdraw { + assets: want.into(), + reserve: want_reserve_chain, + xcm: Xcm(vec![ + //Executed on interlay + BuyExecution { + fees: half(&reserve_fees), + weight_limit: weight_limit.clone(), + }, + DepositReserveAsset { + assets: Wild(AllCounted(max_assets)), + dest, + xcm: Xcm(vec![ + //Executed on acala + BuyExecution { + fees: half(&destination_fee), + weight_limit: weight_limit.clone(), + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]), + }, + ]), + }, + ]); + + let give_reserve_fees = give_asset + .clone() + .reanchored(&give_reserve_chain, origin_context) + .expect("should reanchor"); + + // executed on local (acala) + let message = Xcm(vec![ + WithdrawAsset(give_asset.clone().into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: give_reserve_chain, + xcm: Xcm(vec![ + //Executed on moonbeam + BuyExecution { + fees: half(&give_reserve_fees), + weight_limit: weight_limit.clone(), + }, + DepositReserveAsset { + assets: AllCounted(max_assets).into(), + dest: swap_chain, + xcm, + }, + ]), + }, + ]); + VersionedXcm::V3(message) +} + +fn craft_exchange_asset_xcm, RC: Decode + GetDispatchInfo>( + give: MultiAsset, + want: M, + is_sell: bool, +) -> 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 beneficiary = Junction::AccountId32 { id: BOB, network: None }.into(); + let assets: MultiAssets = MultiAsset::from((GeneralIndex(0), 100 * UNITS)).into(); // hardcoded + let max_assets = assets.len() as u32 + 1; + let context = X2(GlobalConsensus(NetworkId::Polkadot), Parachain(ACALA_PARA_ID)); + let fees = assets + .get(0) + .expect("should have at least 1 asset") + .clone() + .reanchored(&dest, context) + .expect("should reanchor"); + let give = give.reanchored(&dest, context).expect("should reanchor give"); + let give: MultiAssetFilter = Definite(give.into()); + let want = want.into(); + let weight_limit = { + let fees = fees.clone(); + let mut remote_message = Xcm(vec![ + ReserveAssetDeposited::(assets.clone()), + ClearOrigin, + BuyExecution { + fees, + weight_limit: Limited(Weight::zero()), + }, + ExchangeAsset { + give: give.clone(), + want: want.clone(), + maximal: is_sell, + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + 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 remote (on hydra) + let xcm = Xcm(vec![ + BuyExecution { fees, weight_limit }, + ExchangeAsset { + give, + want, + maximal: is_sell, + }, + DepositAsset { + assets: Wild(AllCounted(max_assets)), + beneficiary, + }, + ]); + // executed on local (acala) + let message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + TransferReserveAsset { assets, dest, xcm }, + ]); + VersionedXcm::V3(message) +} From bfc120874c1ad5a7b02dc0fd505c6588ffb32310 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:24:19 +0200 Subject: [PATCH 57/73] clean up in integration tests --- integration-tests/src/exchange_asset.rs | 101 +++++++++------------ integration-tests/src/polkadot_test_net.rs | 1 - 2 files changed, 45 insertions(+), 57 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index d5e33c99d..7743d5158 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -15,6 +15,7 @@ use xcm_emulator::TestExt; pub const SELL: bool = true; pub const BUY: bool = false; +pub const ACA: u32 = 1234; pub const GLMR: u32 = 4567; pub const IBTC: u32 = 7890; @@ -23,38 +24,27 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { //Arrange TestNet::reset(); - let aca = 1234; let mut price = None; Hydra::execute_with(|| { - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"ACA".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(aca), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)) - ))), - None - )); + register_aca(); + + add_currency_price(ACA, FixedU128::from(1)); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); let token_price = FixedU128::from_float(1.0); - assert_ok!(hydradx_runtime::Tokens::deposit(aca, &omnipool_account, 3000 * UNITS)); + assert_ok!(hydradx_runtime::Tokens::deposit(ACA, &omnipool_account, 3000 * UNITS)); assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - aca, + ACA, token_price, Permill::from_percent(100), AccountId::from(BOB), )); use hydradx_traits::pools::SpotPriceProvider; - price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, aca); + price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, ACA); }); Acala::execute_with(|| { @@ -88,14 +78,14 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { let fees = 500801282051; Hydra::execute_with(|| { assert_eq!( - hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 50 * UNITS - fees ); // We receive about 39_101 HDX (HDX is super cheap in our test) let received = 39_101 * UNITS + BOB_INITIAL_NATIVE_BALANCE + 207_131_554_396; assert_eq!(hydradx_runtime::Balances::free_balance(&AccountId::from(BOB)), received); assert_eq!( - hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), + hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()), fees ); }); @@ -106,38 +96,24 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { //Arrange TestNet::reset(); - let aca = 1234; - let mut price = None; Hydra::execute_with(|| { - assert_ok!(hydradx_runtime::AssetRegistry::register( - hydradx_runtime::RuntimeOrigin::root(), - b"ACA".to_vec(), - pallet_asset_registry::AssetType::Token, - 1_000_000, - Some(aca), - None, - Some(hydradx_runtime::AssetLocation(MultiLocation::new( - 1, - X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)) - ))), - None - )); + register_aca(); + + add_currency_price(ACA, FixedU128::from(1)); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); let token_price = FixedU128::from_float(1.0); - assert_ok!(hydradx_runtime::Tokens::deposit(aca, &omnipool_account, 3000 * UNITS)); + assert_ok!(hydradx_runtime::Tokens::deposit(ACA, &omnipool_account, 3000 * UNITS)); assert_ok!(hydradx_runtime::Omnipool::add_token( hydradx_runtime::RuntimeOrigin::root(), - aca, + ACA, token_price, Permill::from_percent(100), AccountId::from(BOB), )); - use hydradx_traits::pools::SpotPriceProvider; - price = hydradx_runtime::Omnipool::spot_price(CORE_ASSET_ID, aca); }); Acala::execute_with(|| { @@ -172,7 +148,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { let swapped = 361693915942; // HDX is super cheap in our setup Hydra::execute_with(|| { assert_eq!( - hydradx_runtime::Tokens::free_balance(aca, &AccountId::from(BOB)), + hydradx_runtime::Tokens::free_balance(ACA, &AccountId::from(BOB)), 100 * UNITS - swapped - fees ); assert_eq!( @@ -180,7 +156,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { BOB_INITIAL_NATIVE_BALANCE + 300 * UNITS ); assert_eq!( - hydradx_runtime::Tokens::free_balance(aca, &hydradx_runtime::Treasury::account_id()), + hydradx_runtime::Tokens::free_balance(ACA, &hydradx_runtime::Treasury::account_id()), fees ); }); @@ -196,14 +172,7 @@ fn transfer_and_swap_should_work_with_4_hops() { register_glmr(); register_ibtc(); - assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( - hydradx_runtime::RuntimeOrigin::root(), - GLMR, - FixedU128::from(1), - )); - - // make sure the price is propagated - hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); + add_currency_price(GLMR, FixedU128::from(1)); init_omnipool(); let omnipool_account = hydradx_runtime::Omnipool::protocol_account(); @@ -247,14 +216,7 @@ fn transfer_and_swap_should_work_with_4_hops() { register_glmr(); register_ibtc(); - assert_ok!(hydradx_runtime::MultiTransactionPayment::add_currency( - hydradx_runtime::RuntimeOrigin::root(), - IBTC, - FixedU128::from(1), - )); - - // make sure the price is propagated - hydradx_runtime::MultiTransactionPayment::on_initialize(hydradx_runtime::System::block_number()); + add_currency_price(IBTC, FixedU128::from(1)); let alice_init_moon_balance = 3000 * UNITS; assert_ok!(hydradx_runtime::Tokens::deposit( @@ -318,6 +280,22 @@ fn register_glmr() { )); } +fn register_aca() { + assert_ok!(hydradx_runtime::AssetRegistry::register( + hydradx_runtime::RuntimeOrigin::root(), + b"ACA".to_vec(), + pallet_asset_registry::AssetType::Token, + 1_000_000, + Some(ACA), + None, + Some(hydradx_runtime::AssetLocation(MultiLocation::new( + 1, + X2(Parachain(ACALA_PARA_ID), GeneralIndex(0)) + ))), + None + )); +} + fn register_ibtc() { assert_ok!(hydradx_runtime::AssetRegistry::register( hydradx_runtime::RuntimeOrigin::root(), @@ -334,6 +312,17 @@ fn register_ibtc() { )); } +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()); +} + /// Returns amount if `asset` is fungible, or zero. fn fungible_amount(asset: &MultiAsset) -> u128 { if let Fungible(amount) = &asset.fun { diff --git a/integration-tests/src/polkadot_test_net.rs b/integration-tests/src/polkadot_test_net.rs index eb226b2f1..f7fa7212f 100644 --- a/integration-tests/src/polkadot_test_net.rs +++ b/integration-tests/src/polkadot_test_net.rs @@ -267,7 +267,6 @@ pub fn hydra_ext() -> sp_io::TestExternalities { (LRNA, Price::from(1)), (DAI, Price::from(1)), (BTC, Price::from_inner(134_000_000)), - (1234, Price::from(1)), ], account_currencies: vec![], } From f7fb997021a50ffcce1c04ecb8dee0ef1936488e Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:49:14 +0200 Subject: [PATCH 58/73] make clippy happy --- integration-tests/src/exchange_asset.rs | 13 ++++++------- runtime/adapters/src/tests/xcm_execute_filter.rs | 10 +++++----- runtime/adapters/src/xcm_execute_filter.rs | 8 ++------ runtime/hydradx/src/xcm.rs | 1 - 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 7743d5158..694e6aca0 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -365,7 +365,7 @@ fn craft_transfer_and_swap_xcm_with_4_hops( .clone() .reanchored(&dest, origin_context) .expect("should reanchor give"); - let give: MultiAssetFilter = Definite(give.clone().into()); + let give: MultiAssetFilter = Definite(give.into()); let want: MultiAssets = want_asset.clone().into(); let fees = give_asset @@ -379,17 +379,16 @@ fn craft_transfer_and_swap_xcm_with_4_hops( .expect("should reanchor"); let destination_fee = want_asset - .clone() .reanchored(&dest, want_reserve_chain.interior) .expect("should reanchor"); let weight_limit = { let fees = fees.clone(); let mut remote_message = Xcm(vec![ - ReserveAssetDeposited::(assets.clone()), + ReserveAssetDeposited::(assets), ClearOrigin, BuyExecution { - fees: fees.clone(), + fees, weight_limit: Limited(Weight::zero()), }, ExchangeAsset { @@ -434,7 +433,7 @@ fn craft_transfer_and_swap_xcm_with_4_hops( weight_limit: weight_limit.clone(), }, ExchangeAsset { - give: give.clone(), + give, want: want.clone(), maximal: is_sell, }, @@ -473,7 +472,7 @@ fn craft_transfer_and_swap_xcm_with_4_hops( // executed on local (acala) let message = Xcm(vec![ - WithdrawAsset(give_asset.clone().into()), + WithdrawAsset(give_asset.into()), InitiateReserveWithdraw { assets: All.into(), reserve: give_reserve_chain, @@ -481,7 +480,7 @@ fn craft_transfer_and_swap_xcm_with_4_hops( //Executed on moonbeam BuyExecution { fees: half(&give_reserve_fees), - weight_limit: weight_limit.clone(), + weight_limit, }, DepositReserveAsset { assets: AllCounted(max_assets).into(), diff --git a/runtime/adapters/src/tests/xcm_execute_filter.rs b/runtime/adapters/src/tests/xcm_execute_filter.rs index e6dcc4841..4f3b568ee 100644 --- a/runtime/adapters/src/tests/xcm_execute_filter.rs +++ b/runtime/adapters/src/tests/xcm_execute_filter.rs @@ -76,7 +76,7 @@ fn xcm_execute_filter_should_allow_a_transfer_and_swap() { fn xcm_execute_filter_should_filter_too_deep_xcm() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.into(); let max_assets = 2; let beneficiary = Junction::AccountId32 { @@ -122,7 +122,7 @@ fn xcm_execute_filter_should_filter_too_deep_xcm() { fn xcm_execute_filter_should_not_filter_message_with_max_deep() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.into(); let max_assets = 2; let beneficiary = Junction::AccountId32 { @@ -170,7 +170,7 @@ fn xcm_execute_filter_should_not_filter_message_with_max_deep() { fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allowed_in_depth() { //Arrange let fees = MultiAsset::from((MultiLocation::here(), 10)); - let assets: MultiAssets = fees.clone().into(); + let assets: MultiAssets = fees.into(); let max_assets = 2; let beneficiary = Junction::AccountId32 { @@ -188,7 +188,7 @@ fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allo let mut message = Xcm(vec![TransferReserveAsset { assets: assets.clone(), dest, - xcm: deposit.clone(), + xcm: deposit, }]); for _ in 0..2 { @@ -218,7 +218,7 @@ fn xcm_execute_filter_should_filter_messages_with_one_more_instruction_than_allo instructions_with_inner_xcms.append(&mut rest); message = Xcm(vec![TransferReserveAsset { - assets: assets.clone(), + assets, dest, xcm: Xcm(instructions_with_inner_xcms.clone()), }]); diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index f16c4fa95..6a9f88a1a 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -16,15 +16,11 @@ where fn allowed_or_recurse(inst: &Instruction) -> Either> { match inst { ClearOrigin - | ClaimAsset { .. } | ExchangeAsset { .. } | WithdrawAsset(..) | TransferAsset { .. } | DepositAsset { .. } - | SetTopic(..) - | ClearTopic | ExpectAsset(..) - | BurnAsset(..) | SetFeesMode { .. } | BuyExecution { .. } => Either::Left(true), InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { @@ -47,8 +43,8 @@ where return false; } let instructions_count = instructions; - let mut iter = xcm.inner().iter(); - while let Some(inst) = iter.next() { + let iter = xcm.inner().iter(); + for inst in iter { instructions_count.set(instructions_count.get() + 1); if instructions_count.get() > MaxInstructions::get() { return false; diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index e0f4a6c87..7da9e9147 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -192,7 +192,6 @@ parameter_types! { } parameter_types! { - pub ReachableDest: Option = Some(Parent.into()); pub const MaxXcmDepth: u16 = 5; pub const MaxNumberOfInstructions: u16 = 100; } From bc897da88f10398d2a31c5ae0ddfe6c9192e0889 Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:53:36 +0200 Subject: [PATCH 59/73] rollback omnipool --- Cargo.lock | 3 --- pallets/omnipool/Cargo.toml | 13 ++++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d40dd9196..171832b1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7052,7 +7052,6 @@ dependencies = [ "log", "orml-tokens", "orml-traits", - "orml-utilities", "pallet-balances", "parity-scale-codec", "pretty_assertions", @@ -7064,8 +7063,6 @@ dependencies = [ "sp-runtime", "sp-std", "sp-tracing", - "xcm", - "xcm-executor", ] [[package]] diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index 8c8eb4370..fcb7e8eb0 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -18,7 +18,6 @@ scale-info = { version = "2.3.1", default-features = false, features = ["derive" codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.4.0" } # primitives -sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -28,7 +27,6 @@ frame-system = { workspace = true } # ORML orml-traits = { workspace = true } -orml-utilities = { workspace = true } # Warehouse hydradx-traits = { workspace = true } @@ -44,12 +42,9 @@ log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } +sp-core = { workspace = true, optional = true} sp-io = { workspace = true, optional = true } -#Polkadot -xcm-executor = { workspace = true } -polkadot-xcm = { workspace = true } - [dev-dependencies] sp-io = { workspace = true } sp-tracing = { workspace = true } @@ -63,20 +58,20 @@ pretty_assertions = "1.2.1" default = ["std"] std = [ "codec/std", - "sp-core/std", - "sp-io/std", "sp-runtime/std", "sp-std/std", "frame-support/std", "frame-system/std", "scale-info/std", + "sp-core/std", + "sp-io/std", "pallet-balances/std", "orml-tokens/std", "frame-benchmarking/std", - "xcm-executor/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", + "sp-core", "sp-io", "pallet-balances", ] From 9a722e0a42bea9a4d5db020d604f1f314cb2b29e Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:55:10 +0200 Subject: [PATCH 60/73] rollback omnipool toml as we moved the exchange asset implementation out of it --- pallets/omnipool/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index fcb7e8eb0..ed04774f3 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -42,10 +42,11 @@ log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { workspace = true, optional = true } pallet-balances = { workspace = true, optional = true } -sp-core = { workspace = true, optional = true} +sp-core = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } [dev-dependencies] +sp-core = { workspace = true, optional = true } sp-io = { workspace = true } sp-tracing = { workspace = true } pallet-balances = { workspace = true } From 7effb1e9034453a08a419a4b05547f9ded2f13fe Mon Sep 17 00:00:00 2001 From: dmoka Date: Mon, 17 Jul 2023 20:55:33 +0200 Subject: [PATCH 61/73] remove unnecessary optional --- pallets/omnipool/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/omnipool/Cargo.toml b/pallets/omnipool/Cargo.toml index ed04774f3..f31536277 100644 --- a/pallets/omnipool/Cargo.toml +++ b/pallets/omnipool/Cargo.toml @@ -46,7 +46,7 @@ sp-core = { workspace = true, optional = true } sp-io = { workspace = true, optional = true } [dev-dependencies] -sp-core = { workspace = true, optional = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-tracing = { workspace = true } pallet-balances = { workspace = true } From 9b106990f9926eb46afdaeee215fb1bbb9556360 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 09:50:21 +0200 Subject: [PATCH 62/73] bump versions --- Cargo.lock | 6 +++--- integration-tests/Cargo.toml | 2 +- runtime/adapters/Cargo.toml | 2 +- runtime/hydradx/Cargo.toml | 2 +- runtime/hydradx/src/lib.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05c744ed5..da578b3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3773,7 +3773,7 @@ dependencies = [ [[package]] name = "hydradx-adapters" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -3814,7 +3814,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "167.0.0" +version = "168.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", @@ -10074,7 +10074,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.8.0" +version = "1.8.1" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 6906a5e87..99e769eac 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.8.0" +version = "1.8.1" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/runtime/adapters/Cargo.toml b/runtime/adapters/Cargo.toml index f03ebf0ea..1c48b38da 100644 --- a/runtime/adapters/Cargo.toml +++ b/runtime/adapters/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-adapters" -version = "0.4.1" +version = "0.4.2" description = "Structs and other generic types for building runtimes." authors = ["GalacticCouncil"] edition = "2021" diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index d0f7424c9..94d88fa24 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "167.0.0" +version = "168.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 8bcf9a6e3..e5bcdf4e7 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -95,7 +95,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 167, + spec_version: 168, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From f2d9008412e2c27dd820361e65faad2d54064a35 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 10:11:11 +0200 Subject: [PATCH 63/73] order functions in execution order --- runtime/adapters/src/xcm_execute_filter.rs | 52 +++++++++++----------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index 6a9f88a1a..21ef834b9 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -13,20 +13,20 @@ where MaxXcmDepth: Get, MaxInstructions: Get; -fn allowed_or_recurse(inst: &Instruction) -> Either> { - match inst { - ClearOrigin - | ExchangeAsset { .. } - | WithdrawAsset(..) - | TransferAsset { .. } - | DepositAsset { .. } - | ExpectAsset(..) - | SetFeesMode { .. } - | BuyExecution { .. } => Either::Left(true), - InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { - Either::Right(xcm) +impl Contains<(MultiLocation, Xcm)> + for AllowTransferAndSwap +where + MaxXcmDepth: Get, + MaxInstructions: Get, +{ + fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { + // allow root to execute XCM + if loc == &MultiLocation::here() { + return true; } - _ => Either::Left(false), + + let instructions_count = Cell::new(0u16); + check_instructions_recursively::(xcm, 0, &instructions_count) } } @@ -69,19 +69,19 @@ where true } -impl Contains<(MultiLocation, Xcm)> - for AllowTransferAndSwap -where - MaxXcmDepth: Get, - MaxInstructions: Get, -{ - fn contains((loc, xcm): &(MultiLocation, Xcm)) -> bool { - // allow root to execute XCM - if loc == &MultiLocation::here() { - return true; +fn allowed_or_recurse(inst: &Instruction) -> Either> { + match inst { + ClearOrigin + | ExchangeAsset { .. } + | WithdrawAsset(..) + | TransferAsset { .. } + | DepositAsset { .. } + | ExpectAsset(..) + | SetFeesMode { .. } + | BuyExecution { .. } => Either::Left(true), + InitiateReserveWithdraw { xcm, .. } | DepositReserveAsset { xcm, .. } | TransferReserveAsset { xcm, .. } => { + Either::Right(xcm) } - - let instructions_count = Cell::new(0u16); - check_instructions_recursively::(xcm, 0, &instructions_count) + _ => Either::Left(false), } } From 7a497894f55f0155165d838994ae4a53bcc21b42 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 10:13:47 +0200 Subject: [PATCH 64/73] organize import --- runtime/hydradx/src/xcm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 70cf7c6bf..6524b16ef 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -14,6 +14,7 @@ use frame_support::{ PalletId, }; use frame_system::EnsureRoot; +use hydradx_adapters::xcm_exchange::XcmAssetExchanger; use hydradx_traits::router::PoolType; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiNativeAsset}; @@ -226,7 +227,6 @@ impl pallet_xcm::Config for Runtime { type ReachableDest = ReachableDest; } pub struct CurrencyIdConvert; -use hydradx_adapters::xcm_exchange::XcmAssetExchanger; use primitives::constants::chain::CORE_ASSET_ID; impl Convert> for CurrencyIdConvert { From 6d7c340d61bf8f4f6104ac294b4a573ce363843f Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 10:52:44 +0200 Subject: [PATCH 65/73] remove unused dependencies --- Cargo.lock | 2 -- runtime/hydradx/Cargo.toml | 4 ---- 2 files changed, 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da578b3eb..0fae4eecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3840,7 +3840,6 @@ dependencies = [ "orml-tokens", "orml-traits", "orml-unknown-tokens", - "orml-utilities", "orml-vesting", "orml-xcm", "orml-xcm-support", @@ -3888,7 +3887,6 @@ dependencies = [ "parachain-info", "parity-scale-codec", "polkadot-parachain", - "pretty_assertions", "primitive-types", "primitives", "scale-info", diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 94d88fa24..00b823e57 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -72,7 +72,6 @@ orml-tokens = { workspace = true } orml-traits = { workspace = true } orml-vesting = { workspace = true } orml-benchmarking = { workspace = true, optional = true } -orml-utilities = { workspace = true } # orml XCM support orml-xtokens = { workspace = true } @@ -127,9 +126,6 @@ sp-trie = { workspace = true } sp-io = { workspace = true } primitive-types = { workspace = true } -[dev-dependencies] -pretty_assertions = "1.4.0" - [features] default = ["std"] runtime-benchmarks = [ From 0d66a99b9bdd7aed381d9b9f82087e9e222c9291 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 10:54:32 +0200 Subject: [PATCH 66/73] undo runtime version dump as nothing will be changed in hydra runtime for now. First we test the feature in basilisk --- Cargo.lock | 2 +- runtime/hydradx/Cargo.toml | 2 +- runtime/hydradx/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fae4eecd..0fa5f9afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3814,7 +3814,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "168.0.0" +version = "167.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 00b823e57..8aff00d07 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "168.0.0" +version = "167.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 e5bcdf4e7..8bcf9a6e3 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -95,7 +95,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 168, + spec_version: 167, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From b59c0379b312e9b3df365f407aa48b43bc4ca833 Mon Sep 17 00:00:00 2001 From: dmoka Date: Tue, 18 Jul 2023 11:09:58 +0200 Subject: [PATCH 67/73] Remove asset exchange configuration from hydra runtime as first we try out the feature on basilisk. --- integration-tests/src/exchange_asset.rs | 6 ++++++ runtime/hydradx/src/xcm.rs | 16 ++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/integration-tests/src/exchange_asset.rs b/integration-tests/src/exchange_asset.rs index 6475a986a..9e50f3bb3 100644 --- a/integration-tests/src/exchange_asset.rs +++ b/integration-tests/src/exchange_asset.rs @@ -19,6 +19,10 @@ pub const ACA: u32 = 1234; pub const GLMR: u32 = 4567; pub const IBTC: u32 = 7890; +//TODO: Unignore these tests when we have the AssetExchange feature configured in hydra. +//For now they are ignored as first we want to try out AssetExchange in basilisk + +#[ignore] #[test] fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { //Arrange @@ -91,6 +95,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_sell() { }); } +#[ignore] #[test] fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { //Arrange @@ -163,6 +168,7 @@ fn hydra_should_swap_assets_when_receiving_from_acala_with_buy() { } //We swap GLMR for iBTC, sent from ACALA and executed on Hydradx, resultin in 4 hops +#[ignore] #[test] fn transfer_and_swap_should_work_with_4_hops() { //Arrange diff --git a/runtime/hydradx/src/xcm.rs b/runtime/hydradx/src/xcm.rs index 6524b16ef..9669b5ad9 100644 --- a/runtime/hydradx/src/xcm.rs +++ b/runtime/hydradx/src/xcm.rs @@ -14,8 +14,6 @@ use frame_support::{ PalletId, }; use frame_system::EnsureRoot; -use hydradx_adapters::xcm_exchange::XcmAssetExchanger; -use hydradx_traits::router::PoolType; use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiNativeAsset}; use pallet_xcm::XcmPassthrough; @@ -30,8 +28,6 @@ use xcm_builder::{ }; use xcm_executor::{Config, XcmExecutor}; -use hydradx_adapters::xcm_execute_filter::AllowTransferAndSwap; - #[derive(Debug, Default, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct AssetLocation(pub polkadot_xcm::v3::MultiLocation); @@ -85,10 +81,7 @@ parameter_types! { pub const MaxInstructions: u32 = 100; pub const MaxAssetsForTransfer: usize = 2; - pub DefaultPoolType: PoolType = PoolType::Omnipool; - pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); - pub TempAccount: AccountId = [42; 32].into(); } pub struct XcmConfig; @@ -121,7 +114,7 @@ impl Config for XcmConfig { type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetLocker = (); - type AssetExchanger = XcmAssetExchanger; + type AssetExchanger = (); type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; @@ -197,11 +190,6 @@ parameter_types! { pub ReachableDest: Option = Some(Parent.into()); } -parameter_types! { - pub const MaxXcmDepth: u16 = 5; - pub const MaxNumberOfInstructions: u16 = 100; -} - impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -209,7 +197,7 @@ impl pallet_xcm::Config for Runtime { type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = XcmRouter; type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = AllowTransferAndSwap; + type XcmExecuteFilter = Everything; type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; From f908c068f612042f51ee3e30b303ed93ca7112cd Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 25 Jul 2023 11:26:00 +0200 Subject: [PATCH 68/73] simplify check Co-authored-by: Martin Hloska --- runtime/adapters/src/xcm_execute_filter.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index 21ef834b9..dfc22e617 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -54,13 +54,11 @@ where Either::Left(true) => continue, Either::Left(false) => return false, Either::Right(xcm) => { - if check_instructions_recursively::( + if !check_instructions_recursively::( xcm, depth + 1, instructions_count, ) { - continue; - } else { return false; } } From 5a41c808d72b7efd94287749e643a46b2e53c87d Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 25 Jul 2023 11:34:07 +0200 Subject: [PATCH 69/73] simplify iter Co-authored-by: Martin Hloska --- runtime/adapters/src/xcm_execute_filter.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index dfc22e617..b06f31adc 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -43,8 +43,7 @@ where return false; } let instructions_count = instructions; - let iter = xcm.inner().iter(); - for inst in iter { + for inst in xcm.inner().iter() { instructions_count.set(instructions_count.get() + 1); if instructions_count.get() > MaxInstructions::get() { return false; From dfe223ef9408eb0904ad441c522ac0e063177394 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 25 Jul 2023 11:34:43 +0200 Subject: [PATCH 70/73] initialize vec with capacity --- runtime/adapters/src/xcm_exchange.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 856a69b03..0c88448ce 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -102,7 +102,7 @@ where asset_out, }], )?; - let mut assets = sp_std::vec::Vec::new(); + let mut assets = sp_std::vec::Vec::with_capacity(2); let left_over = Currency::free_balance(asset_in, &account); if left_over > Runtime::Balance::zero() { Currency::withdraw(asset_in, &account, left_over)?; // burn left over tokens From e4baf9325597f650218dd7f45f1770e087d53faf Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 25 Jul 2023 11:37:37 +0200 Subject: [PATCH 71/73] rename DefaultPoolType --- runtime/adapters/src/xcm_exchange.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 0c88448ce..2231273f6 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -8,19 +8,19 @@ use sp_std::marker::PhantomData; use sp_std::vec; use xcm_executor::traits::AssetExchange; -pub struct XcmAssetExchanger( - PhantomData<(Runtime, TempAccount, CurrencyIdConvert, Currency, DefaultPoolType)>, +pub struct XcmAssetExchanger( + PhantomData<(Runtime, TempAccount, CurrencyIdConvert, Currency, Pool)>, ); -impl AssetExchange - for XcmAssetExchanger +impl AssetExchange + for XcmAssetExchanger where Runtime: pallet_route_executor::Config, TempAccount: Get, CurrencyIdConvert: Convert>, Currency: MultiCurrency, Runtime::Balance: From + Zero + Into, - DefaultPoolType: Get>, + Pool: Get>, { fn exchange_asset( _origin: Option<&MultiLocation>, @@ -65,7 +65,7 @@ where amount.into(), min_buy_amount.into(), vec![Trade { - pool: DefaultPoolType::get(), + pool: Pool::get(), asset_in, asset_out, }], @@ -97,7 +97,7 @@ where amount.into(), max_sell_amount.into(), vec![Trade { - pool: DefaultPoolType::get(), + pool: Pool::get(), asset_in, asset_out, }], From c370f50f7f0a7e65eb6e1840ae202401055b52c0 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Thu, 27 Jul 2023 11:02:54 +0200 Subject: [PATCH 72/73] add doc comments and simplify --- runtime/adapters/src/xcm_execute_filter.rs | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/runtime/adapters/src/xcm_execute_filter.rs b/runtime/adapters/src/xcm_execute_filter.rs index b06f31adc..24a010350 100644 --- a/runtime/adapters/src/xcm_execute_filter.rs +++ b/runtime/adapters/src/xcm_execute_filter.rs @@ -6,12 +6,11 @@ use polkadot_xcm::v3::prelude::*; use sp_core::Get; use sp_runtime::Either; +/// Meant to serve as an `XcmExecuteFilter` for `pallet_xcm` by allowing XCM instructions related to transferring and +/// exchanging assets while disallowing e.g. `Transact`. pub struct AllowTransferAndSwap( PhantomData<(MaxXcmDepth, MaxInstructions, RuntimeCall)>, -) -where - MaxXcmDepth: Get, - MaxInstructions: Get; +); impl Contains<(MultiLocation, Xcm)> for AllowTransferAndSwap @@ -30,6 +29,9 @@ where } } +/// Recurses depth-first through the instructions of an XCM and checks whether they are allowed, limiting both recursion +/// depth (via `MaxXcmDepth`) and instructions (`MaxInstructions`). +/// See [`allowed_or_recurse`] for the filter list. fn check_instructions_recursively( xcm: &Xcm, depth: u16, @@ -42,10 +44,9 @@ where if depth > MaxXcmDepth::get() { return false; } - let instructions_count = instructions; for inst in xcm.inner().iter() { - instructions_count.set(instructions_count.get() + 1); - if instructions_count.get() > MaxInstructions::get() { + instructions.set(instructions.get() + 1); + if instructions.get() > MaxInstructions::get() { return false; } @@ -53,11 +54,7 @@ where Either::Left(true) => continue, Either::Left(false) => return false, Either::Right(xcm) => { - if !check_instructions_recursively::( - xcm, - depth + 1, - instructions_count, - ) { + if !check_instructions_recursively::(xcm, depth + 1, instructions) { return false; } } @@ -66,6 +63,8 @@ where true } +/// Check if an XCM instruction is allowed (returning `Left(true)`), disallowed (`Left(false)`) or needs recursion to +/// determine whether it is allowed (`Right(xcm)`). fn allowed_or_recurse(inst: &Instruction) -> Either> { match inst { ClearOrigin From 9c00160338a02f9431224e0e59aaf1dd334c20d3 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Thu, 27 Jul 2023 13:54:21 +0200 Subject: [PATCH 73/73] add doc comment for XcmAssetExchanger --- runtime/adapters/src/xcm_exchange.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runtime/adapters/src/xcm_exchange.rs b/runtime/adapters/src/xcm_exchange.rs index 2231273f6..fb0a508da 100644 --- a/runtime/adapters/src/xcm_exchange.rs +++ b/runtime/adapters/src/xcm_exchange.rs @@ -8,6 +8,14 @@ use sp_std::marker::PhantomData; use sp_std::vec; use xcm_executor::traits::AssetExchange; +/// Implements `AssetExchange` to support the `ExchangeAsset` XCM instruction. +/// +/// Uses pallet-route-executor to execute trades. +/// +/// Will map exchange instructions with `maximal = true` to sell (selling all of `give` asset) and `false` to buy +/// (buying exactly `want` amount of asset). +/// +/// NOTE: Currenty limited to one asset each for `give` and `want`. pub struct XcmAssetExchanger( PhantomData<(Runtime, TempAccount, CurrencyIdConvert, Currency, Pool)>, );