diff --git a/primitives/xcm/src/asset_id_conversions.rs b/primitives/xcm/src/asset_id_conversions.rs index 089fc3cc32..ca0e8c3924 100644 --- a/primitives/xcm/src/asset_id_conversions.rs +++ b/primitives/xcm/src/asset_id_conversions.rs @@ -49,7 +49,8 @@ where AssetIdInfoGetter: AssetTypeGetter, { fn convert(id: &xcm::v4::Location) -> Option { - let v3_location = xcm_builder::WithLatestLocationConverter::::convert(id)?; + let v3_location = + xcm_builder::WithLatestLocationConverter::::convert(id)?; AssetIdInfoGetter::get_asset_id(v3_location.clone().into()) } fn convert_back(what: &AssetId) -> Option { @@ -66,7 +67,8 @@ where AssetIdInfoGetter: AssetTypeGetter, { fn convert_location(id: &xcm::v4::Location) -> Option { - let v3_location = xcm_builder::WithLatestLocationConverter::::convert(id)?; + let v3_location = + xcm_builder::WithLatestLocationConverter::::convert(id)?; AssetIdInfoGetter::get_asset_id(v3_location.clone().into()) } } diff --git a/primitives/xcm/src/constants.rs b/primitives/xcm/src/constants.rs index 2a9cbfa670..01f534ebdb 100644 --- a/primitives/xcm/src/constants.rs +++ b/primitives/xcm/src/constants.rs @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Moonbeam. If not, see . -pub const MAX_ASSETS: u32 = 64; +pub const MAX_ASSETS: u32 = 20; diff --git a/primitives/xcm/src/ethereum_xcm.rs b/primitives/xcm/src/ethereum_xcm.rs index 8aee89c34a..ba1e13f7d0 100644 --- a/primitives/xcm/src/ethereum_xcm.rs +++ b/primitives/xcm/src/ethereum_xcm.rs @@ -79,7 +79,7 @@ pub struct EthereumXcmTransactionV1 { pub gas_limit: U256, /// Fee configuration of choice. pub fee_payment: EthereumXcmFee, - /// Either a Call (the callee, account or contract address) or Create (currently unsupported). + /// Either a Call (the callee, account or contract address) or Create (unsupported for v1). pub action: TransactionAction, /// Value to be transfered. pub value: U256, @@ -93,7 +93,7 @@ pub struct EthereumXcmTransactionV1 { pub struct EthereumXcmTransactionV2 { /// Gas limit to be consumed by EVM execution. pub gas_limit: U256, - /// Either a Call (the callee, account or contract address) or Create (currently unsupported). + /// Either a Call (the callee, account or contract address) or Create). pub action: TransactionAction, /// Value to be transfered. pub value: U256, @@ -104,20 +104,34 @@ pub struct EthereumXcmTransactionV2 { } pub trait XcmToEthereum { - fn into_transaction_v2(&self, nonce: U256, chain_id: u64) -> Option; + fn into_transaction_v2( + &self, + nonce: U256, + chain_id: u64, + allow_create: bool, + ) -> Option; } impl XcmToEthereum for EthereumXcmTransaction { - fn into_transaction_v2(&self, nonce: U256, chain_id: u64) -> Option { + fn into_transaction_v2( + &self, + nonce: U256, + chain_id: u64, + allow_create: bool, + ) -> Option { match self { - EthereumXcmTransaction::V1(v1_tx) => v1_tx.into_transaction_v2(nonce, chain_id), - EthereumXcmTransaction::V2(v2_tx) => v2_tx.into_transaction_v2(nonce, chain_id), + EthereumXcmTransaction::V1(v1_tx) => { + v1_tx.into_transaction_v2(nonce, chain_id, allow_create) + } + EthereumXcmTransaction::V2(v2_tx) => { + v2_tx.into_transaction_v2(nonce, chain_id, allow_create) + } } } } impl XcmToEthereum for EthereumXcmTransactionV1 { - fn into_transaction_v2(&self, nonce: U256, chain_id: u64) -> Option { + fn into_transaction_v2(&self, nonce: U256, chain_id: u64, _: bool) -> Option { // We dont support creates for now if self.action == TransactionAction::Create { return None; @@ -195,9 +209,14 @@ impl XcmToEthereum for EthereumXcmTransactionV1 { } impl XcmToEthereum for EthereumXcmTransactionV2 { - fn into_transaction_v2(&self, nonce: U256, chain_id: u64) -> Option { - // We dont support creates for now - if self.action == TransactionAction::Create { + fn into_transaction_v2( + &self, + nonce: U256, + chain_id: u64, + allow_create: bool, + ) -> Option { + if !allow_create && self.action == TransactionAction::Create { + // Create not allowed return None; } let from_tuple_to_access_list = |t: &Vec<(H160, Vec)>| -> AccessList { @@ -274,7 +293,10 @@ mod tests { s: H256::from_low_u64_be(1u64), })); - assert_eq!(xcm_transaction.into_transaction_v2(nonce, 111), expected_tx); + assert_eq!( + xcm_transaction.into_transaction_v2(nonce, 111, false), + expected_tx + ); } #[test] @@ -302,7 +324,10 @@ mod tests { signature: TransactionSignature::new(42, rs_id(), rs_id()).unwrap(), })); - assert_eq!(xcm_transaction.into_transaction_v2(nonce, 111), expected_tx); + assert_eq!( + xcm_transaction.into_transaction_v2(nonce, 111, false), + expected_tx + ); } #[test] fn test_eip_2930_v1() { @@ -344,7 +369,10 @@ mod tests { s: H256::from_low_u64_be(1u64), })); - assert_eq!(xcm_transaction.into_transaction_v2(nonce, 111), expected_tx); + assert_eq!( + xcm_transaction.into_transaction_v2(nonce, 111, false), + expected_tx + ); } #[test] @@ -373,6 +401,9 @@ mod tests { s: H256::from_low_u64_be(1u64), })); - assert_eq!(xcm_transaction.into_transaction_v2(nonce, 111), expected_tx); + assert_eq!( + xcm_transaction.into_transaction_v2(nonce, 111, false), + expected_tx + ); } } diff --git a/primitives/xcm/src/fee_handlers.rs b/primitives/xcm/src/fee_handlers.rs deleted file mode 100644 index c496bc37c9..0000000000 --- a/primitives/xcm/src/fee_handlers.rs +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright 2019-2022 PureStake Inc. -// This file is part of Moonbeam. - -// Moonbeam is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonbeam is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonbeam. If not, see . - -// We need to know how to charge for incoming assets -// We assume AssetIdInfoGetter is implemented and is capable of getting how much units we should -// charge for a given asset -// This takes the first fungible asset, and takes whatever UnitPerSecondGetter establishes -// UnitsToWeightRatio trait, which needs to be implemented by AssetIdInfoGetter - -use cumulus_primitives_core::XcmContext; -use frame_support::{ - pallet_prelude::Weight, - traits::{tokens::fungibles::Mutate, Get}, - weights::constants::WEIGHT_REF_TIME_PER_SECOND, -}; -use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; -use xcm::latest::{Asset, AssetId as xcmAssetId, Error as XcmError, Fungibility, Location}; - -use xcm_builder::TakeRevenue; -use xcm_executor::traits::{MatchesFungibles, WeightTrader}; - -pub struct FirstAssetTrader< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, ->( - Weight, - Option<(Location, u128, u128)>, // id, amount, units_per_second - PhantomData<(AssetType, AssetIdInfoGetter, R)>, -); -impl< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, - > WeightTrader for FirstAssetTrader -{ - fn new() -> Self { - FirstAssetTrader(Weight::zero(), None, PhantomData) - } - fn buy_weight( - &mut self, - weight: Weight, - payment: xcm_executor::AssetsInHolding, - _context: &XcmContext, - ) -> Result { - // can only call one time - if self.1.is_some() { - // TODO: better error - return Err(XcmError::NotWithdrawable); - } - - assert_eq!(self.0, Weight::zero()); - let first_asset = payment - .clone() - .fungible_assets_iter() - .next() - .ok_or(XcmError::TooExpensive)?; - - // We are only going to check first asset for now. This should be sufficient for simple token - // transfers. We will see later if we change this. - match (first_asset.id, first_asset.fun) { - (xcmAssetId(location), Fungibility::Fungible(_)) => { - let asset_type: AssetType = location - .clone() - .try_into() - .map_err(|_| XcmError::InvalidLocation)?; - // Shortcut if we know the asset is not supported - // This involves the same db read per block, mitigating any attack based on - // non-supported assets - if !AssetIdInfoGetter::payment_is_supported(asset_type.clone()) { - return Err(XcmError::TooExpensive); - } - if let Some(units_per_second) = AssetIdInfoGetter::get_units_per_second(asset_type) - { - // TODO handle proof size payment - let amount = units_per_second.saturating_mul(weight.ref_time() as u128) - / (WEIGHT_REF_TIME_PER_SECOND as u128); - - // We dont need to proceed if the amount is 0 - // For cases (specially tests) where the asset is very cheap with respect - // to the weight needed - if amount.is_zero() { - return Ok(payment); - } - - let required = Asset { - fun: Fungibility::Fungible(amount), - id: xcmAssetId(location.clone()), - }; - let unused = payment - .checked_sub(required) - .map_err(|_| XcmError::TooExpensive)?; - - self.0 = weight; - self.1 = Some((location, amount, units_per_second)); - - return Ok(unused); - } else { - return Err(XcmError::TooExpensive); - }; - } - _ => return Err(XcmError::TooExpensive), - } - } - - // Refund weight. We will refund in whatever asset is stored in self. - fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option { - if let Some((location, prev_amount, units_per_second)) = self.1.clone() { - let weight = weight.min(self.0); - self.0 -= weight; - let amount = units_per_second * (weight.ref_time() as u128) - / (WEIGHT_REF_TIME_PER_SECOND as u128); - let amount = amount.min(prev_amount); - self.1 = Some(( - location.clone(), - prev_amount.saturating_sub(amount), - units_per_second, - )); - Some(Asset { - fun: Fungibility::Fungible(amount), - id: xcmAssetId(location.clone()), - }) - } else { - None - } - } -} - -/// Deal with spent fees, deposit them as dictated by R -impl< - AssetType: TryFrom + Clone, - AssetIdInfoGetter: UnitsToWeightRatio, - R: TakeRevenue, - > Drop for FirstAssetTrader -{ - fn drop(&mut self) { - if let Some((id, amount, _)) = self.1.clone() { - if amount > 0 { - R::take_revenue((id, amount).into()); - } - } - } -} - -/// XCM fee depositor to which we implement the TakeRevenue trait -/// It receives a fungibles::Mutate implemented argument, a matcher to convert Asset into -/// AssetId and amount, and the fee receiver account -pub struct XcmFeesToAccount( - PhantomData<(Assets, Matcher, AccountId, ReceiverAccount)>, -); -impl< - Assets: Mutate, - Matcher: MatchesFungibles, - AccountId: Clone + Eq, - ReceiverAccount: Get, - > TakeRevenue for XcmFeesToAccount -{ - fn take_revenue(revenue: Asset) { - match Matcher::matches_fungibles(&revenue) { - Ok((asset_id, amount)) => { - let ok = Assets::mint_into(asset_id, &ReceiverAccount::get(), amount).is_ok(); - debug_assert!(ok, "`mint_into` cannot generally fail; qed"); - } - Err(_) => log::debug!( - target: "xcm", - "take revenue failed matching fungible" - ), - } - } -} - -// Defines the trait to obtain the units per second of a give asset_type for local execution -// This parameter will be used to charge for fees upon asset_type deposit -pub trait UnitsToWeightRatio { - // Whether payment in a particular asset_type is suppotrted - fn payment_is_supported(asset_type: AssetType) -> bool; - // Get units per second from asset type - fn get_units_per_second(asset_type: AssetType) -> Option; - #[cfg(feature = "runtime-benchmarks")] - fn set_units_per_second(_asset_type: AssetType, _fee_per_second: u128) {} -} - -#[cfg(test)] -mod test { - use super::*; - use cumulus_primitives_core::XcmHash; - use xcm::latest::{AssetId, Fungibility, Junction, Junctions}; - use xcm_executor::AssetsInHolding; - - const ARBITRARY_ML: Location = Location { - parents: 0u8, - interior: Junctions::Here, - }; - const ARBITRARY_ID: AssetId = AssetId(ARBITRARY_ML); - - impl UnitsToWeightRatio for () { - fn payment_is_supported(_asset_type: Location) -> bool { - true - } - fn get_units_per_second(_asset_type: Location) -> Option { - // return WEIGHT_REF_TIME_PER_SECOND to cancel the division out in buy_weight() - // this should make weight and payment amounts directly comparable - Some(WEIGHT_REF_TIME_PER_SECOND as u128) - } - } - - #[test] - fn test_buy_weight_accounts_weight_properly() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: Junctions::Here, - }; - payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - } - - #[test] - fn cant_call_buy_weight_twice() { - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - - // should be able to buy once - let mut asset_one_payment = AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into(), - }; - asset_one_payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(100u128), - }); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - let buy_one_results = trader - .buy_weight(100u64.into(), asset_one_payment.clone(), &ctx) - .expect("can buy weight once"); - assert_eq!(buy_one_results.fungible.len(), 0); // no unused amount - assert_eq!(trader.0, 100u64.into()); - assert_eq!( - trader.1, - Some(( - Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into() - }, - 100, - WEIGHT_REF_TIME_PER_SECOND as u128 - )) - ); - - // but not twice - let mut asset_two_payment = xcm_executor::AssetsInHolding::new(); - let location = Location { - parents: 0u8, - interior: [Junction::Parachain(1001)].into(), - }; - asset_two_payment.subsume(Asset { - id: AssetId(location.clone()), - fun: Fungibility::Fungible(10_000u128), - }); - let ctx = XcmContext { - origin: Some(location), - message_id: XcmHash::default(), - topic: None, - }; - assert_eq!( - trader.buy_weight(10_000u64.into(), asset_two_payment.clone(), &ctx), - Err(XcmError::NotWithdrawable), - ); - - // state should be unchanged - assert_eq!(trader.0, 100u64.into()); - assert_eq!( - trader.1, - Some(( - Location { - parents: 0u8, - interior: [Junction::Parachain(1000)].into() - }, - 100, - WEIGHT_REF_TIME_PER_SECOND as u128 - )) - ); - } - - #[test] - fn can_call_refund_weight_with_all_weight() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - assert_eq!( - trader.refund_weight(1000u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - } - - #[test] - fn can_call_refund_multiple_times() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - assert_eq!( - trader.refund_weight(100u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(100), - id: ARBITRARY_ID, - }) - ); - - // should reflect 100 weight and 100 currency deducted - assert_eq!(trader.0, 900u64.into()); - assert_eq!(trader.1.clone().unwrap().1, 900); - - // can call again - assert_eq!( - trader.refund_weight(200u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(200), - id: ARBITRARY_ID, - }) - ); - - // should reflect another 200 weight and 200 currency deducted - assert_eq!(trader.0, 700u64.into()); - assert_eq!(trader.1.clone().unwrap().1, 700); - } - - #[test] - fn refund_weight_caps_weight() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - // can't call with more weight - assert_eq!( - trader.refund_weight(9999u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - assert_eq!(trader.0, Weight::zero()); - } - - #[test] - fn refund_weight_caps_currency() { - let amount = 1000u128; - - let mut payment = AssetsInHolding::new(); - payment.subsume(Asset { - id: ARBITRARY_ID, - fun: Fungibility::Fungible(amount), - }); - - let ctx = XcmContext { - origin: Some(ARBITRARY_ML), - message_id: XcmHash::default(), - topic: None, - }; - let mut trader: FirstAssetTrader = FirstAssetTrader::new(); - let unused = trader - .buy_weight((amount as u64).into(), payment.clone(), &ctx) - .expect("can buy weight once"); - assert!(unused.is_empty()); - assert_eq!(trader.0, 1000u64.into()); - - // adjust weight so that it will allow a higher amount -- we want to see that the currency - // (self.1.1) is capped even when weight is not - trader.0 = trader.0.saturating_add(1000u64.into()); - - // can't call with more weight - assert_eq!( - trader.refund_weight(1500u64.into(), &ctx), - Some(Asset { - fun: Fungibility::Fungible(1000), - id: ARBITRARY_ID, - }) - ); - assert_eq!(trader.0, 500u64.into()); // still thinks we have unreturned weight - } -} diff --git a/primitives/xcm/src/lib.rs b/primitives/xcm/src/lib.rs index d61f62d7e3..d63f13327d 100644 --- a/primitives/xcm/src/lib.rs +++ b/primitives/xcm/src/lib.rs @@ -24,9 +24,6 @@ pub use asset_id_conversions::*; mod constants; pub use constants::*; -mod fee_handlers; -pub use fee_handlers::*; - mod origin_conversion; pub use origin_conversion::*; diff --git a/primitives/xcm/src/origin_conversion.rs b/primitives/xcm/src/origin_conversion.rs index 8f47727079..a215627486 100644 --- a/primitives/xcm/src/origin_conversion.rs +++ b/primitives/xcm/src/origin_conversion.rs @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Moonbeam. If not, see . -use frame_support::traits::{Get, OriginTrait}; +use frame_support::traits::{ContainsPair, Get, OriginTrait}; use orml_traits::location::{RelativeReserveProvider, Reserve}; use sp_runtime::traits::TryConvert; use sp_std::{convert::TryInto, marker::PhantomData}; -use xcm::latest::{Asset, Junction::AccountKey20, Location, NetworkId}; +use xcm::latest::{Asset, AssetId, Fungibility, Junction::AccountKey20, Location, NetworkId}; /// Instructs how to convert a 20 byte accountId into a Location pub struct AccountIdToLocation(sp_std::marker::PhantomData); @@ -82,3 +82,23 @@ where }) } } + +/// Matches foreign assets from a given origin. +/// Foreign assets are assets bridged from other consensus systems. i.e parents > 1. +pub struct IsBridgedConcreteAssetFrom(PhantomData); +impl ContainsPair for IsBridgedConcreteAssetFrom +where + Origin: Get, +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = Origin::get(); + &loc == origin + && matches!( + asset, + Asset { + id: AssetId(Location { parents: 2, .. }), + fun: Fungibility::Fungible(_) + }, + ) + } +}