From 6db2cb9c7748da9dc53ef9cdf937afb352df21e5 Mon Sep 17 00:00:00 2001 From: Gianfranco Tasteri Date: Tue, 19 Dec 2023 13:12:57 -0300 Subject: [PATCH] use custom asset transactor to handle automation pallet call --- Cargo.lock | 1 + runtime/common/Cargo.toml | 2 + runtime/common/src/custom_transactor.rs | 158 +++++++++++++++++++ runtime/common/src/custom_xcm_barrier.rs | 136 ---------------- runtime/common/src/lib.rs | 2 +- runtime/integration-tests/src/test_macros.rs | 5 - runtime/pendulum/src/xcm_config.rs | 108 ++++--------- 7 files changed, 194 insertions(+), 218 deletions(-) create mode 100644 runtime/common/src/custom_transactor.rs delete mode 100644 runtime/common/src/custom_xcm_barrier.rs diff --git a/Cargo.lock b/Cargo.lock index 4f1526047..d35bdc13b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10071,6 +10071,7 @@ dependencies = [ "frame-system", "orml-asset-registry", "orml-traits", + "orml-xcm-support", "parity-scale-codec", "paste", "scale-info", diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 6ab9ec789..7bc227474 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -28,6 +28,7 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot", default-feature 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" } @@ -54,6 +55,7 @@ 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..9502583aa --- /dev/null +++ b/runtime/common/src/custom_transactor.rs @@ -0,0 +1,158 @@ + +use sp_runtime::codec::FullCodec; +use sp_runtime::{ + traits::{Convert, MaybeSerializeDeserialize, SaturatedConversion}, +}; +use sp_std::{ + cmp::{Eq, PartialEq}, + fmt::Debug, + marker::PhantomData, + prelude::*, + result, +}; + +use xcm::v3::{prelude::*, Error as XcmError, MultiAsset, MultiLocation, Result}; +use xcm_executor::{ + traits::{Convert as MoreConvert, MatchesFungible, TransactAsset}, + Assets, +}; +use orml_xcm_support::{UnknownAsset as UnknownAssetT,OnDepositFail}; + + +/// Asset transaction errors. +enum Error { + /// Failed to match fungible. + FailedToMatchFungible, + /// `MultiLocation` to `AccountId` Conversion failed. + AccountIdConversionFailed, + /// `CurrencyId` conversion failed. + CurrencyIdConversionFailed, +} + +impl From for XcmError { + fn from(e: Error) -> Self { + match e { + Error::FailedToMatchFungible => XcmError::FailedToTransactAsset("FailedToMatchFungible"), + Error::AccountIdConversionFailed => XcmError::FailedToTransactAsset("AccountIdConversionFailed"), + Error::CurrencyIdConversionFailed => XcmError::FailedToTransactAsset("CurrencyIdConversionFailed"), + } + } +} + +pub trait AutomationPalletConfig { + fn matches_asset(asset: &MultiAsset) -> Option; + fn matches_beneficiary(beneficiary_location: &MultiLocation) -> Option<(u8, [u8;32])>; + fn callback(length: u8, data: [u8;32], amount: u128) -> Result; +} + +#[allow(clippy::type_complexity)] +pub struct CustomMultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + AutomationPalletConfigT, +>( + PhantomData<( + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + AutomationPalletConfigT, + )>, +); + +impl< + MultiCurrency: orml_traits::MultiCurrency, + UnknownAsset: UnknownAssetT, + Match: MatchesFungible, + AccountId: sp_std::fmt::Debug + Clone, + AccountIdConvert: MoreConvert, + CurrencyId: FullCodec + Eq + PartialEq + Copy + MaybeSerializeDeserialize + Debug, + CurrencyIdConvert: Convert>, + DepositFailureHandler: OnDepositFail, + AutomationPalletConfigT: AutomationPalletConfig + > TransactAsset + for CustomMultiCurrencyAdapter< + MultiCurrency, + UnknownAsset, + Match, + AccountId, + AccountIdConvert, + CurrencyId, + CurrencyIdConvert, + DepositFailureHandler, + AutomationPalletConfigT, + > +{ + fn deposit_asset(asset: &MultiAsset, location: &MultiLocation, _context: &XcmContext) -> Result { + if let Some(amount_deposited) = AutomationPalletConfigT::matches_asset(asset){ + + if let Some((length,data)) = AutomationPalletConfigT::matches_beneficiary(location){ + AutomationPalletConfigT::callback(length, data, amount_deposited); + return Ok(()); + } + } + match ( + AccountIdConvert::convert_ref(location), + CurrencyIdConvert::convert(asset.clone()), + Match::matches_fungible(asset), + ) { + // known asset + (Ok(who), Some(currency_id), Some(amount)) => MultiCurrency::deposit(currency_id, &who, amount) + .or_else(|err| DepositFailureHandler::on_deposit_currency_fail(err, currency_id, &who, amount)), + // unknown asset + _ => UnknownAsset::deposit(asset, location) + .or_else(|err| DepositFailureHandler::on_deposit_unknown_asset_fail(err, asset, location)), + } + } + + fn withdraw_asset( + asset: &MultiAsset, + location: &MultiLocation, + _maybe_context: Option<&XcmContext>, + ) -> result::Result { + UnknownAsset::withdraw(asset, location).or_else(|_| { + let who = AccountIdConvert::convert_ref(location) + .map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let currency_id = CurrencyIdConvert::convert(asset.clone()) + .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; + let amount: MultiCurrency::Balance = Match::matches_fungible(asset) + .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? + .saturated_into(); + MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into())) + })?; + + Ok(asset.clone().into()) + } + + fn transfer_asset( + asset: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + _context: &XcmContext, + ) -> result::Result { + let from_account = + AccountIdConvert::convert_ref(from).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let to_account = + AccountIdConvert::convert_ref(to).map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?; + let currency_id = CurrencyIdConvert::convert(asset.clone()) + .ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?; + let amount: MultiCurrency::Balance = Match::matches_fungible(asset) + .ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))? + .saturated_into(); + MultiCurrency::transfer(currency_id, &from_account, &to_account, amount) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + + Ok(asset.clone().into()) + } +} + diff --git a/runtime/common/src/custom_xcm_barrier.rs b/runtime/common/src/custom_xcm_barrier.rs deleted file mode 100644 index 31b8c3711..000000000 --- a/runtime/common/src/custom_xcm_barrier.rs +++ /dev/null @@ -1,136 +0,0 @@ -use core::marker::PhantomData; -use frame_support::{ensure, log, traits::Contains}; - -use xcm::{latest::{prelude::*, Instruction, Weight as XCMWeight}}; - -use xcm_executor::traits::ShouldExecute; - -use scale_info::prelude::boxed::Box; -use sp_std::vec::Vec; - -pub trait ReserveAssetDepositedMatcher: Sync { - fn matches(&self, multi_asset: &MultiAsset) -> Option; -} - -pub trait DepositAssetMatcher { - fn matches<'a>( - &self, - assets: &'a MultiAssetFilter, - beneficiary: &'a MultiLocation, - ) -> Option<(u8, &'a [u8])>; -} -pub struct MatcherPair { - reserve_deposited_asset_matcher: Box, - deposit_asset_matcher: Box, -} - -impl MatcherPair { - pub fn new( - reserve_deposited_asset_matcher: Box, - deposit_asset_matcher: Box, - ) -> Self { - MatcherPair { reserve_deposited_asset_matcher, deposit_asset_matcher } - } - - fn matches_reserve_deposited(&self, multi_asset: &MultiAsset) -> Option { - self.reserve_deposited_asset_matcher.matches(multi_asset) - } - - fn matches_deposit_asset<'a>( - &'a self, - assets: &'a MultiAssetFilter, - beneficiary: &'a MultiLocation, - ) -> Option<(u8, &'a [u8])> { - self.deposit_asset_matcher.matches(assets, beneficiary) - } -} - -pub trait MatcherConfig { - fn get_matcher_pairs() -> Vec; - fn callback(length: u8, data: &[u8], amount: u128) -> Result<(), ()>; - fn get_incoming_parachain_id() -> u32; - fn extract_fee(location: MultiLocation, amount: u128)-> u128; -} - -pub struct AllowUnpaidExecutionFromCustom { - _phantom: PhantomData<(T, V)>, -} -impl, V: MatcherConfig> ShouldExecute - for AllowUnpaidExecutionFromCustom -{ - fn should_execute( - origin: &MultiLocation, - instructions: &mut [Instruction], - _max_weight: XCMWeight, - _weight_credit: &mut XCMWeight, - ) -> Result<(), ()> { - log::info!( - target: "xcm::barriers", - "AllowUnpaidExecutionFromCustom origin: {:?}, instructions: {:?}, max_weight: {:?}, weight_credit: {:?}", - origin, instructions, _max_weight, _weight_credit, - ); - log::info!("origin {:?}", origin); - let incoming_parachain_id = V::get_incoming_parachain_id(); - - ensure!(T::contains(origin), ()); - - // Check if the origin is the specific parachain - if let MultiLocation { parents: 1, interior: X1(Parachain(parachain_id)) } = origin { - log::info!("paraid {:?}", *parachain_id); - if *parachain_id == incoming_parachain_id { - log::info!("parachain match"); - - let matcher_pairs = V::get_matcher_pairs(); - // Iterate through the instructions, for - // each match pair we allow - for matcher_pair in matcher_pairs { - let mut reserve_deposited_asset_matched: Option = None; - let mut amount_to_process: u128 = 0; - - - // Check for ReserveAssetDeposited instruction - for instruction in instructions.iter() { - if let Instruction::ReserveAssetDeposited(assets) = instruction { - - for asset in assets.clone().into_inner().iter() { - if let Some(matched_asset) = matcher_pair.matches_reserve_deposited(asset) { - - // Check if the matched asset is fungible and extract the amount - if let MultiAsset { - id: Concrete(location), - fun: Fungibility::Fungible(amount_deposited), - } = matched_asset - { - reserve_deposited_asset_matched = Some(location); - amount_to_process = amount_deposited; - break; // Break as the matched asset is found and amount is extracted - } - } - } - } - } - - // If ReserveAssetDeposited matches, then check for DepositAsset with the same matcher pair - // and execute the callback - if let Some(location) = reserve_deposited_asset_matched { - for instruction in instructions.iter() { - if let Instruction::DepositAsset { assets, beneficiary } = instruction { - if let Some((length, data)) = - matcher_pair.matches_deposit_asset(assets, beneficiary) - { - - let amount_to_deposit = V::extract_fee(location, amount_to_process); - V::callback(length, data,amount_to_deposit); - return Err(()) - } - } - } - } - } - } - } - - - Ok(()) - } -} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 2eac8b8a4..375a919a5 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -8,7 +8,7 @@ use sp_runtime::{ pub mod asset_registry; pub mod chain_ext; -pub mod custom_xcm_barrier; +pub mod custom_transactor; mod proxy_type; pub mod stellar; pub mod zenlink; diff --git a/runtime/integration-tests/src/test_macros.rs b/runtime/integration-tests/src/test_macros.rs index c06de58ee..fff87474e 100644 --- a/runtime/integration-tests/src/test_macros.rs +++ b/runtime/integration-tests/src/test_macros.rs @@ -668,11 +668,6 @@ macro_rules! moonbeam_transfers_token_and_handle_automation { RuntimeEvent::System(frame_system::Event::Remarked { .. }) ))); - // Assert also that the fee has been given to the treasury id - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::Tokens(orml_tokens::Event::Deposited { .. }) - ))); }); }}; diff --git a/runtime/pendulum/src/xcm_config.rs b/runtime/pendulum/src/xcm_config.rs index 0d6e0e146..d42b6d774 100644 --- a/runtime/pendulum/src/xcm_config.rs +++ b/runtime/pendulum/src/xcm_config.rs @@ -6,28 +6,22 @@ use frame_support::{ }; use orml_traits::{ location::{RelativeReserveProvider, Reserve}, - parameter_type_with_key, MultiCurrency -}; + parameter_type_with_key,}; use orml_xcm_support::{DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter}; use pallet_xcm::XcmPassthrough; use polkadot_parachain::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; -use sp_runtime::{Perbill,traits::Convert}; -use sp_std::{vec, vec::Vec}; -use scale_info::prelude::boxed::Box; +use sp_runtime::{traits::Convert}; use xcm::latest::{prelude::*, Weight as XCMWeight}; use xcm_builder::{ - AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, + AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds,AllowUnpaidExecutionFrom, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, UsingComponents, }; use xcm_executor::{traits::ShouldExecute, XcmExecutor}; use runtime_common::{ - custom_xcm_barrier::{ - AllowUnpaidExecutionFromCustom, DepositAssetMatcher, MatcherConfig, MatcherPair, - ReserveAssetDepositedMatcher, - }, + custom_transactor::{CustomMultiCurrencyAdapter, AutomationPalletConfig}, parachains::polkadot::{asset_hub, equilibrium, moonbeam, polkadex}, }; @@ -156,17 +150,7 @@ 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 @@ -269,82 +253,54 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { } // We will allow for BRZ location from moonbeam -struct BRZMoonbeamAssetMatcher; -impl ReserveAssetDepositedMatcher for BRZMoonbeamAssetMatcher { - fn matches(&self, multi_asset: &MultiAsset) -> Option { +pub struct AutomationPalletConfigPendulum; + +impl AutomationPalletConfig for AutomationPalletConfigPendulum { + fn matches_asset(asset: &MultiAsset) -> Option { let expected_multiloc = moonbeam::BRZ_location(); - match multi_asset { - MultiAsset { id: AssetId::Concrete(loc), .. } if loc == &expected_multiloc => - return Some(multi_asset.clone()), + 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 -struct PendulumAutomationPalletMatcher; -impl DepositAssetMatcher for PendulumAutomationPalletMatcher { - fn matches<'a>( - &self, - assets: &'a MultiAssetFilter, - beneficiary: &'a MultiLocation, - ) -> Option<(u8, &'a [u8])> { - if let ( - Wild(AllCounted(1)), + // TODO modify with automation's pallet instance + fn matches_beneficiary(beneficiary_location: &MultiLocation) -> Option<(u8, [u8;32])> { + if let MultiLocation { parents: 0, interior: X2(PalletInstance(99), GeneralKey { length, data }), - }, - ) = (assets, beneficiary) + } + = beneficiary_location { - Some((*length, &*data)) + Some((*length, *data)) } else { None } } -} -pub struct MatcherConfigPendulum; - -impl MatcherConfig for MatcherConfigPendulum { - fn get_matcher_pairs() -> Vec { - vec![ - MatcherPair::new( - Box::new(BRZMoonbeamAssetMatcher), - Box::new(PendulumAutomationPalletMatcher), - ), - // Additional matcher pairs to be defined in the future - ] - } - fn get_incoming_parachain_id() -> u32 { - moonbeam::PARA_ID - } - - fn callback(_length: u8, _data: &[u8], _amount: u128) -> Result<(), ()> { + 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(()) } - // TODO define actual desired implementation of fee extraction. - // In this placeholder implementation, some percentage is given to treasury account - // in the local currency representing the asset from - // ReserveDepositAsset (the original asset) - fn extract_fee(location: MultiLocation, amount: u128)-> u128 { - if let Some(currency_id) = CurrencyIdConvert::convert(location){ - - let fee = Perbill::from_percent(1) * amount; - >::deposit(currency_id, &Treasury::account_id(), fee); - amount - fee - }else{ - amount - } - - } } - -pub type Barrier = AllowUnpaidExecutionFromCustom; +/// Means for transacting the currencies of this parachain +pub type LocalAssetTransactor = CustomMultiCurrencyAdapter< + Currencies, + (), // We don't handle unknown assets. + IsNativeConcrete, + AccountId, + LocationToAccountId, + CurrencyId, + CurrencyIdConvert, + DepositToAlternative, + AutomationPalletConfigPendulum, +>; +pub type Barrier = AllowUnpaidExecutionFrom; pub struct XcmConfig; impl xcm_executor::Config for XcmConfig {