diff --git a/polkadot/xcm/xcm-builder/src/tests.rs b/polkadot/xcm/xcm-builder/src/tests.rs
index 37878f082e..ceff66a2a8 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 .
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::::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,
+ // trader two
+ FixedRateOfFungible,
+ );
+
+ 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 8c9e6ec636..8d962c88e5 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 {
- Err(Error::Unimplemented)
+
+ fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result {
+ 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 {
+ for_tuples!( #(
+ if let Some(asset) = Tuple.refund_weight(weight) {
+ return Some(asset);
+ }
+ )* );
+ None
}
}