diff --git a/polkadot/xcm/xcm-builder/src/tests.rs b/polkadot/xcm/xcm-builder/src/tests.rs index 37878f082e09ba02afd2165c1144a665457fcbd1..ceff66a2a828b44abda267ac8e1f9353148759e9 100644 --- a/polkadot/xcm/xcm-builder/src/tests.rs +++ b/polkadot/xcm/xcm-builder/src/tests.rs @@ -15,6 +15,7 @@ // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. use super::{mock::*, *}; +use frame_support::{assert_err, weights::constants::WEIGHT_PER_SECOND}; use xcm::latest::prelude::*; use xcm_executor::{traits::*, Config, XcmExecutor}; @@ -383,3 +384,52 @@ fn prepaid_result_of_query_should_get_free_execution() { let r = XcmExecutor::<TestConfig>::execute_xcm(origin.clone(), message.clone(), weight_limit); assert_eq!(r, Outcome::Incomplete(10, XcmError::Barrier)); } + +fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { + (AssetId::from(location), Fungibility::Fungible(amount)).into() +} + +#[test] +fn weight_trader_tuple_should_work() { + pub const PARA_1: MultiLocation = X1(Parachain(1)); + pub const PARA_2: MultiLocation = X1(Parachain(2)); + + parameter_types! { + pub static HereWeightPrice: (AssetId, u128) = (Here.into(), WEIGHT_PER_SECOND.into()); + pub static PARA1WeightPrice: (AssetId, u128) = (PARA_1.into(), WEIGHT_PER_SECOND.into()); + } + + type Traders = ( + // trader one + FixedRateOfFungible<HereWeightPrice, ()>, + // trader two + FixedRateOfFungible<PARA1WeightPrice, ()>, + ); + + let mut traders = Traders::new(); + // trader one buys weight + assert_eq!( + traders.buy_weight(5, fungible_multi_asset(Here, 10).into()), + Ok(fungible_multi_asset(Here, 5).into()), + ); + // trader one refunds + assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(Here, 2))); + + let mut traders = Traders::new(); + // trader one failed; trader two buys weight + assert_eq!( + traders.buy_weight(5, fungible_multi_asset(PARA_1, 10).into()), + Ok(fungible_multi_asset(PARA_1, 5).into()), + ); + // trader two refunds + assert_eq!(traders.refund_weight(2), Some(fungible_multi_asset(PARA_1, 2))); + + let mut traders = Traders::new(); + // all traders fails + assert_err!( + traders.buy_weight(5, fungible_multi_asset(PARA_2, 10).into()), + XcmError::TooExpensive, + ); + // and no refund + assert_eq!(traders.refund_weight(2), None); +} diff --git a/polkadot/xcm/xcm-executor/src/traits/weight.rs b/polkadot/xcm/xcm-executor/src/traits/weight.rs index 8c9e6ec6366d944158a0c6a623730f3e72868afd..8d962c88e5ece8a60ae6e2d37c04ea954eee4f22 100644 --- a/polkadot/xcm/xcm-executor/src/traits/weight.rs +++ b/polkadot/xcm/xcm-executor/src/traits/weight.rs @@ -58,6 +58,11 @@ pub trait UniversalWeigher { } /// Charge for weight in order to execute XCM. +/// +/// A `WeightTrader` may also be put into a tuple, in which case the default behavior of +/// `buy_weight` and `refund_weight` would be to attempt to call each tuple element's own +/// implementation of these two functions, in the order of which they appear in the tuple, +/// returning early when a successful result is returned. pub trait WeightTrader: Sized { /// Create a new trader instance. fn new() -> Self; @@ -76,11 +81,31 @@ pub trait WeightTrader: Sized { } } -impl WeightTrader for () { +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl WeightTrader for Tuple { fn new() -> Self { - () + for_tuples!( ( #( Tuple::new() ),* ) ) } - fn buy_weight(&mut self, _: Weight, _: Assets) -> Result<Assets, Error> { - Err(Error::Unimplemented) + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> { + let mut last_error = None; + for_tuples!( #( + match Tuple.buy_weight(weight, payment.clone()) { + Ok(assets) => return Ok(assets), + Err(e) => { last_error = Some(e) } + } + )* ); + let last_error = last_error.unwrap_or(Error::TooExpensive); + log::trace!(target: "xcm::buy_weight", "last_error: {:?}", last_error); + Err(last_error) + } + + fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> { + for_tuples!( #( + if let Some(asset) = Tuple.refund_weight(weight) { + return Some(asset); + } + )* ); + None } }