diff --git a/Cargo.lock b/Cargo.lock index e1a7a16a4..eba0e2641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10073,6 +10073,7 @@ dependencies = [ "frame-system", "orml-asset-registry", "orml-traits", + "orml-xcm-support", "parity-scale-codec", "paste", "scale-info", @@ -10082,6 +10083,7 @@ dependencies = [ "sp-std", "spacewalk-primitives 1.0.0 (git+https://github.com/pendulum-chain/spacewalk?rev=20a3dd191dc352f989f90a1a48eacb8ff6d9ac85)", "xcm", + "xcm-executor", "zenlink-protocol", ] diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 977aecc86..7bc227474 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -24,9 +24,11 @@ sp-consensus-aura = { git = "https://github.com/paritytech/substrate", default-f sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.40" } xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.40" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.40" } orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.40" } orml-asset-registry = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.40" } +orml-xcm-support = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.40" } dia-oracle = { git = "https://github.com/pendulum-chain/oracle-pallet", default-features = false, branch = "polkadot-v0.9.40" } zenlink-protocol = { git = "https://github.com/pendulum-chain/Zenlink-DEX-Module", default-features = false, branch = "polkadot-v0.9.40-protocol" } @@ -49,9 +51,11 @@ std = [ "sp-consensus-aura/std", "sp-core/std", "xcm/std", + "xcm-executor/std", "orml-traits/std", "dia-oracle/std", "orml-asset-registry/std", + "orml-xcm-support/std", "zenlink-protocol/std", "spacewalk-primitives/std", ] diff --git a/runtime/common/src/custom_transactor.rs b/runtime/common/src/custom_transactor.rs new file mode 100644 index 000000000..65917b6b2 --- /dev/null +++ b/runtime/common/src/custom_transactor.rs @@ -0,0 +1,74 @@ +use sp_runtime::{ + codec::FullCodec, + traits::{Convert, MaybeSerializeDeserialize, SaturatedConversion}, +}; +use sp_std::{ + cmp::{Eq, PartialEq}, + fmt::Debug, + marker::PhantomData, + prelude::*, + result, +}; + +use orml_xcm_support::{OnDepositFail, UnknownAsset as UnknownAssetT}; +use xcm::v3::{prelude::*, Error as XcmError, MultiAsset, MultiLocation, Result}; +use xcm_executor::{ + traits::{Convert as MoreConvert, MatchesFungible, TransactAsset}, + Assets, +}; + +pub struct AssetData { + pub length: u8, + pub data: [u8; 32], +} + +pub trait AutomationPalletConfig { + fn matches_asset(asset: &MultiAsset) -> Option; + fn matches_beneficiary(beneficiary_location: &MultiLocation) -> Option; + fn callback(length: u8, data: [u8; 32], amount: u128) -> Result; +} + +pub struct CustomTransactorInterceptor( + PhantomData<(WrappedTransactor, AutomationPalletConfigT)>, +); + +impl + TransactAsset for CustomTransactorInterceptor +{ + fn deposit_asset( + asset: &MultiAsset, + location: &MultiLocation, + _context: &XcmContext, + ) -> Result { + if let (Some(amount_deposited), Some(asset_data)) = ( + AutomationPalletConfigT::matches_asset(asset), + AutomationPalletConfigT::matches_beneficiary(location), + ) { + AutomationPalletConfigT::callback( + asset_data.length, + asset_data.data, + amount_deposited, + )?; + return Ok(()) + } + + WrappedTransactor::deposit_asset(asset, location, _context) + } + + fn withdraw_asset( + asset: &MultiAsset, + location: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> result::Result { + WrappedTransactor::withdraw_asset(asset, location, _maybe_context) + } + + fn transfer_asset( + asset: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + _context: &XcmContext, + ) -> result::Result { + WrappedTransactor::transfer_asset(asset, from, to, _context) + } +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index d021bbf19..375a919a5 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -8,6 +8,7 @@ use sp_runtime::{ pub mod asset_registry; pub mod chain_ext; +pub mod custom_transactor; mod proxy_type; pub mod stellar; pub mod zenlink; diff --git a/runtime/foucoco/Cargo.toml b/runtime/foucoco/Cargo.toml index cde17e84b..9e288e986 100644 --- a/runtime/foucoco/Cargo.toml +++ b/runtime/foucoco/Cargo.toml @@ -266,7 +266,7 @@ runtime-benchmarks = [ "replace/runtime-benchmarks", "stellar-relay/runtime-benchmarks", "vault-registry/runtime-benchmarks", - + "oracle/testing-utils", "runtime-common/runtime-benchmarks", "orml-currencies-allowance-extension/runtime-benchmarks", "orml-tokens-management-extension/runtime-benchmarks" diff --git a/runtime/integration-tests/src/amplitude_tests.rs b/runtime/integration-tests/src/amplitude_tests.rs index 25aad2601..ac2801bda 100644 --- a/runtime/integration-tests/src/amplitude_tests.rs +++ b/runtime/integration-tests/src/amplitude_tests.rs @@ -2,7 +2,8 @@ use crate::{ mock::{kusama_relay_ext, para_ext, ParachainType, USDT_ASSET_ID}, sibling, test_macros::{ - parachain1_transfer_asset_to_parachain2, parachain1_transfer_asset_to_parachain2_and_back, + moonbeam_transfers_token_and_handle_automation, parachain1_transfer_asset_to_parachain2, + parachain1_transfer_asset_to_parachain2_and_back, parachain1_transfer_incorrect_asset_to_parachain2_should_fail, transfer_10_relay_token_from_parachain_to_relay_chain, transfer_20_relay_token_from_relay_chain_to_parachain, diff --git a/runtime/integration-tests/src/mock.rs b/runtime/integration-tests/src/mock.rs index f4d14b510..eb6a36b6b 100644 --- a/runtime/integration-tests/src/mock.rs +++ b/runtime/integration-tests/src/mock.rs @@ -10,6 +10,7 @@ use sp_io::TestExternalities; use sp_runtime::traits::AccountIdConversion; use xcm_emulator::Weight; +use runtime_common::parachains::polkadot::moonbeam::PARA_ID as MOONBEAM_PARA_ID; use statemine_runtime as kusama_asset_hub_runtime; use statemint_runtime as polkadot_asset_hub_runtime; @@ -116,6 +117,7 @@ pub enum ParachainType { Pendulum, Amplitude, Sibling, + Moonbeam, } pub struct ExtBuilderParachain { @@ -189,6 +191,7 @@ pub fn para_ext(chain: ParachainType) -> sp_io::TestExternalities { ParachainType::Amplitude => ExtBuilderParachain::amplitude_default().balances(vec![]).build(), ParachainType::Sibling => ExtBuilderParachain::sibling_default().balances(vec![]).build(), + ParachainType::Moonbeam => ExtBuilderParachain::moonbeam_default().balances(vec![]).build(), } } @@ -200,6 +203,7 @@ impl ExtBuilderParachain { ParachainType::Pendulum => PENDULUM_ID, ParachainType::Sibling => SIBLING_ID, ParachainType::Amplitude => AMPLITUDE_ID, + ParachainType::Moonbeam => MOONBEAM_PARA_ID, } } } @@ -255,6 +259,10 @@ impl ExtBuilderParachain { pub fn sibling_default() -> Self { Self { balances: vec![], chain: ParachainType::Sibling } } + + pub fn moonbeam_default() -> Self { + Self { balances: vec![], chain: ParachainType::Moonbeam } + } } impl Builder for ExtBuilderParachain { @@ -276,6 +284,17 @@ impl Builder for ExtBuilderParachain { SiblingCurrencyId ) }, + ParachainType::Moonbeam => { + use sibling::{Runtime, System}; + build_parachain_with_orml!( + self, + Runtime, + System, + NATIVE_INITIAL_BALANCE, + ORML_INITIAL_BALANCE, + SiblingCurrencyId + ) + }, _ => panic!("cannot use this chain to build"), } } diff --git a/runtime/integration-tests/src/pendulum_tests.rs b/runtime/integration-tests/src/pendulum_tests.rs index 79caa871c..01b189592 100644 --- a/runtime/integration-tests/src/pendulum_tests.rs +++ b/runtime/integration-tests/src/pendulum_tests.rs @@ -2,7 +2,8 @@ use crate::{ mock::{para_ext, polkadot_relay_ext, ParachainType, USDT_ASSET_ID}, sibling, test_macros::{ - parachain1_transfer_asset_to_parachain2, parachain1_transfer_asset_to_parachain2_and_back, + moonbeam_transfers_token_and_handle_automation, parachain1_transfer_asset_to_parachain2, + parachain1_transfer_asset_to_parachain2_and_back, parachain1_transfer_incorrect_asset_to_parachain2_should_fail, transfer_10_relay_token_from_parachain_to_relay_chain, transfer_20_relay_token_from_relay_chain_to_parachain, @@ -12,6 +13,7 @@ use crate::{ }; use frame_support::assert_ok; +use runtime_common::parachains::polkadot::moonbeam::PARA_ID as MOONBEAM_PARA_ID; use statemint_runtime as polkadot_asset_hub_runtime; use xcm::latest::NetworkId; use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; @@ -56,12 +58,23 @@ decl_test_parachain! { } } +decl_test_parachain! { + pub struct MoonbeamParachain { + Runtime = sibling::Runtime, + RuntimeOrigin = sibling::RuntimeOrigin, + XcmpMessageHandler = sibling::XcmpQueue, + DmpMessageHandler = sibling::DmpQueue, + new_ext = para_ext(ParachainType::Moonbeam), + } +} + decl_test_network! { pub struct PolkadotMockNet { relay_chain = PolkadotRelay, parachains = vec![ (1000, AssetHubParachain), (2094, PendulumParachain), + (2004, MoonbeamParachain), (9999, SiblingParachain), ], } @@ -142,3 +155,16 @@ fn transfer_native_token_from_pendulum_to_sibling_parachain_and_back() { SIBLING_ID ); } + +#[test] +fn moonbeam_transfers_token_and_handle_automation() { + moonbeam_transfers_token_and_handle_automation!( + PolkadotMockNet, + pendulum_runtime, + PendulumParachain, + sibling, + MoonbeamParachain, + PENDULUM_ID, + MOONBEAM_PARA_ID + ); +} diff --git a/runtime/integration-tests/src/sibling.rs b/runtime/integration-tests/src/sibling.rs index 5ac86b728..30f05b4fb 100644 --- a/runtime/integration-tests/src/sibling.rs +++ b/runtime/integration-tests/src/sibling.rs @@ -45,6 +45,8 @@ use xcm_builder::{ use crate::{AMPLITUDE_ID, ASSETHUB_ID, PENDULUM_ID}; +use runtime_common::parachains::polkadot::moonbeam::BRZ_location; + const XCM_ASSET_RELAY_DOT: u8 = 0; const XCM_ASSET_ASSETHUB_USDT: u8 = 1; @@ -92,6 +94,7 @@ pub enum CurrencyId { Amplitude, Native, XCM(u8), + Token, } // Convert from u32 parachain id to CurrencyId @@ -127,6 +130,7 @@ impl Convert> for CurrencyIdConvert { Some(MultiLocation::new(1, X2(Parachain(PENDULUM_ID), PalletInstance(10)))), CurrencyId::Amplitude => Some(MultiLocation::new(1, X2(Parachain(AMPLITUDE_ID), PalletInstance(10)))), + CurrencyId::Token => Some(BRZ_location()), CurrencyId::XCM(f) => match f { XCM_ASSET_RELAY_DOT => Some(MultiLocation::parent()), // Handles both Kusama and Polkadot asset hub @@ -155,6 +159,7 @@ impl Convert> for CurrencyIdConvert { Some(CurrencyId::XCM(XCM_ASSET_ASSETHUB_USDT)), MultiLocation { parents: 1, interior: Here } => Some(CurrencyId::XCM(XCM_ASSET_RELAY_DOT)), + loc if loc == BRZ_location() => Some(CurrencyId::Token), _ => None, } } diff --git a/runtime/integration-tests/src/test_macros.rs b/runtime/integration-tests/src/test_macros.rs index 45b638062..85e713bff 100644 --- a/runtime/integration-tests/src/test_macros.rs +++ b/runtime/integration-tests/src/test_macros.rs @@ -538,6 +538,10 @@ macro_rules! transfer_native_token_from_parachain1_to_parachain2_and_back { // Verify BOB's balance on parachain2 after receiving // Should increase by the transfer amount $parachain2::execute_with(|| { + use $parachain2_runtime::{RuntimeEvent, System, XTokens}; + for i in System::events().iter() { + println!("para 2 events {}: {:?}\n", stringify!($para2_runtime), i); + } assert_eq!( $parachain2_runtime::Tokens::balance(para1_native_currency_on_para2, &BOB.into()), transfer_amount @@ -591,6 +595,10 @@ macro_rules! transfer_native_token_from_parachain1_to_parachain2_and_back { // Verify ALICE's balance on parachain1 after receiving // Should become the same amount as initial balance before both transfers $parachain1::execute_with(|| { + use $parachain1_runtime::System; + for i in System::events().iter() { + println!("para 1 events {}: {:?}\n", stringify!($para2_runtime), i); + } assert_eq!( $parachain1_runtime::Currencies::free_balance(Parachain1CurrencyId::Native, &ALICE.into()), native_tokens_before @@ -599,7 +607,72 @@ macro_rules! transfer_native_token_from_parachain1_to_parachain2_and_back { }}; } +macro_rules! moonbeam_transfers_token_and_handle_automation { + ( + $mocknet:ident, + $parachain1_runtime:ident, + $parachain1:ident, + $parachain2_runtime:ident, + $parachain2:ident, + $parachain1_id:ident, + $parachain2_id:ident + ) => {{ + use crate::mock::{units, ALICE}; + use polkadot_core_primitives::Balance; + use xcm::latest::{ + Junction, Junction::{ GeneralKey, PalletInstance}, Junctions::{X1,X2, X3}, MultiLocation, WeightLimit, + }; + use $parachain2_runtime::CurrencyId as Parachain2CurrencyId; + + $mocknet::reset(); + + let transfer_amount: Balance = units(10); + // We mock parachain 2 as beeing moonriver in this case. + // Sending "Token" variant which is equivalent to BRZ mock token Multilocation + // in the sibling definition + $parachain2::execute_with(|| { + use $parachain2_runtime::{XTokens, Tokens,RuntimeOrigin, System}; + + assert_ok!(Tokens::set_balance(RuntimeOrigin::root().into(), ALICE.clone().into(), Parachain2CurrencyId::Token,transfer_amount, 0)); + + // We must ensure that the destination Multilocation is of the structure + // the intercept excepts so it calls automation pallet + + // TODO replace instance 99 with automation pallet index when added + assert_ok!(XTokens::transfer( + $parachain2_runtime::RuntimeOrigin::signed(ALICE.into()), + Parachain2CurrencyId::Token, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X3( + Junction::Parachain($parachain1_id), + PalletInstance(99), + GeneralKey {length:32 , data:[1u8;32]} + ) + ) + .into() + ), + WeightLimit::Unlimited + )); + }); + + $parachain1::execute_with(|| { + use $parachain1_runtime::{RuntimeEvent, System, Tokens}; + // given the configuration in pendulum's xcm_config, we expect the callback (in this case a Remark) + // to be executed + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::System(frame_system::Event::Remarked { .. }) + ))); + + + }); + }}; +} // macros defined at the bottom of this file to prevent unresolved imports +pub(super) use moonbeam_transfers_token_and_handle_automation; pub(super) use parachain1_transfer_asset_to_parachain2; pub(super) use parachain1_transfer_asset_to_parachain2_and_back; pub(super) use parachain1_transfer_incorrect_asset_to_parachain2_should_fail; diff --git a/runtime/pendulum/src/xcm_config.rs b/runtime/pendulum/src/xcm_config.rs index 24f205723..67278aba6 100644 --- a/runtime/pendulum/src/xcm_config.rs +++ b/runtime/pendulum/src/xcm_config.rs @@ -21,7 +21,10 @@ use xcm_builder::{ }; use xcm_executor::{traits::ShouldExecute, XcmExecutor}; -use runtime_common::parachains::polkadot::{asset_hub, equilibrium, moonbeam, polkadex}; +use runtime_common::{ + custom_transactor::{AssetData, AutomationPalletConfig, CustomTransactorInterceptor}, + parachains::polkadot::{asset_hub, equilibrium, moonbeam, polkadex}, +}; use crate::{ assets::{ @@ -38,7 +41,7 @@ use crate::{ use super::{ AccountId, Balance, Balances, Currencies, CurrencyId, ParachainInfo, ParachainSystem, PendulumTreasuryAccount, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - WeightToFee, XcmpQueue, + System, Treasury, WeightToFee, XcmpQueue, }; parameter_types! { @@ -150,18 +153,6 @@ where } } -/// Means for transacting the currencies of this parachain -pub type LocalAssetTransactor = MultiCurrencyAdapter< - Currencies, - (), // We don't handle unknown assets. - IsNativeConcrete, - AccountId, - LocationToAccountId, - CurrencyId, - CurrencyIdConvert, - DepositToAlternative, ->; - /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can /// biases the kind of local `Origin` it will become. @@ -262,6 +253,57 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { } } +// We will allow for BRZ location from moonbeam +pub struct AutomationPalletConfigPendulum; + +impl AutomationPalletConfig for AutomationPalletConfigPendulum { + fn matches_asset(asset: &MultiAsset) -> Option { + let expected_multiloc = moonbeam::BRZ_location(); + + match asset { + MultiAsset { + id: AssetId::Concrete(loc), + fun: Fungibility::Fungible(amount_deposited), + } if loc == &expected_multiloc => return Some(*amount_deposited), + _ => return None, + } + } + + // TODO modify with automation's pallet instance + fn matches_beneficiary(beneficiary_location: &MultiLocation) -> Option { + if let MultiLocation { + parents: 0, + interior: X2(PalletInstance(99), GeneralKey { length, data }), + } = beneficiary_location + { + let asset_data = AssetData { length: *length, data: *data }; + Some(asset_data) + } else { + None + } + } + + fn callback(_length: u8, _data: [u8; 32], _amount: u128) -> Result<(), XcmError> { + // TODO change to call the actual automation pallet, with data and length + System::remark_with_event(RuntimeOrigin::signed(AccountId::from([0; 32])), [0; 1].to_vec()); + Ok(()) + } +} + +/// Means for transacting the currencies of this parachain +type Transactor = MultiCurrencyAdapter< + Currencies, + (), // We don't handle unknown assets. + IsNativeConcrete, + AccountId, + LocationToAccountId, + CurrencyId, + CurrencyIdConvert, + DepositToAlternative, +>; + +pub type LocalAssetTransactor = + CustomTransactorInterceptor; pub type Barrier = AllowUnpaidExecutionFrom; pub struct XcmConfig;