Unverified Commit 9254b199 authored by Gavin Wood's avatar Gavin Wood Committed by GitHub
Browse files

Add XCM Origin and converter (#2896)



* Add XCM Origin and converter

* IsMajority filter can be location-prefixed

* Update xcm/pallet-xcm/src/lib.rs
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Update xcm/src/v0/multi_location.rs
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Introduce UsingComponents to allow reuse of fee payment in XCM

* Use Drop rather than finalize

* Add errors for weight.

* Apply suggestions from code review
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Fixes

* Update xcm/xcm-builder/src/weight.rs
Co-authored-by: default avatarKian Paimani <5588131+kianenigma@users.noreply.github.com>

* Various XCM fixes and improvements

* Fixes

* Update xcm/xcm-builder/src/weight.rs
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Update xcm/xcm-builder/src/weight.rs
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Update xcm/xcm-builder/src/weight.rs
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>

* Fixes
Co-authored-by: default avatarAlexander Popiak <alexander.popiak@parity.io>
Co-authored-by: default avatarKian Paimani <5588131+kianenigma@users.noreply.github.com>
parent 944f4f44
Pipeline #135930 failed with stages
in 26 minutes and 43 seconds
This diff is collapsed.
......@@ -92,7 +92,7 @@ use xcm_builder::{
AccountId32Aliases, ChildParachainConvertsVia, SovereignSignedViaLocation,
CurrencyAdapter as XcmCurrencyAdapter, ChildParachainAsNative, SignedAccountId32AsNative,
ChildSystemParachainAsSuperuser, LocationInverter, IsConcrete, FixedWeightBounds,
FixedRateOfConcreteFungible, BackingToPlurality, SignedToAccountId32
BackingToPlurality, SignedToAccountId32, UsingComponents,
};
use constants::{time::*, currency::*, fee::*, size::*};
use frame_support::traits::InstanceFilter;
......@@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("rococo"),
impl_name: create_runtime_str!("parity-rococo-v1.5"),
authoring_version: 0,
spec_version: 231,
spec_version: 232,
impl_version: 0,
#[cfg(not(feature = "disable-runtime-api"))]
apis: RUNTIME_API_VERSIONS,
......@@ -646,7 +646,7 @@ impl xcm_executor::Config for XcmConfig {
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
type Trader = FixedRateOfConcreteFungible<RocFee>;
type Trader = UsingComponents<WeightToFee, RocLocation, AccountId, Balances, ToAuthor<Runtime>>;
type ResponseHandler = ();
}
......@@ -657,7 +657,8 @@ parameter_types! {
/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location
/// of this chain.
pub type LocalOriginToLocation = (
// We allow an origin from the Collective pallet to be used in XCM as a corresponding Plurality
// We allow an origin from the Collective pallet to be used in XCM as a corresponding Plurality of the
// `Unit` body.
BackingToPlurality<Origin, pallet_collective::Origin<Runtime>, CollectiveBodyId>,
// And a usual Signed origin to be used in XCM as a corresponding AccountId32
SignedToAccountId32<Origin, AccountId, RococoNetwork>,
......@@ -881,8 +882,8 @@ impl pallet_collective::Config for Runtime {
type Event = Event;
type MotionDuration = MotionDuration;
type MaxProposals = MaxProposals;
type MaxMembers = MaxMembers;
type DefaultVote = pallet_collective::PrimeDefaultVote;
type MaxMembers = MaxMembers;
type WeightInfo = ();
}
......
......@@ -14,6 +14,7 @@ frame-support = { git = "https://github.com/paritytech/substrate", default-featu
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
xcm = { path = "..", default-features = false }
xcm-executor = { path = "../xcm-executor", default-features = false }
[features]
default = ["std"]
......
......@@ -20,7 +20,8 @@
use sp_std::{marker::PhantomData, convert::TryInto, boxed::Box};
use codec::{Encode, Decode};
use xcm::v0::{BodyId, MultiLocation::{self, X1}, Junction::Plurality};
use xcm::v0::{BodyId, OriginKind, MultiLocation, Junction::Plurality};
use xcm_executor::traits::ConvertOrigin;
use sp_runtime::{RuntimeDebug, traits::BadOrigin};
use frame_support::traits::{EnsureOrigin, OriginTrait, Filter, Get};
......@@ -62,6 +63,7 @@ pub mod pallet {
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Attempted(xcm::v0::Outcome),
Sent(MultiLocation, MultiLocation, Xcm<()>),
}
#[pallet::error]
......@@ -78,11 +80,12 @@ pub mod pallet {
#[pallet::weight(1_000)]
fn send(origin: OriginFor<T>, dest: MultiLocation, message: Xcm<()>) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Self::send_xcm(origin_location, dest, message)
Self::send_xcm(origin_location.clone(), dest.clone(), message.clone())
.map_err(|e| match e {
XcmError::CannotReachDestination(..) => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
})?;
Self::deposit_event(Event::Sent(origin_location, dest, message));
Ok(())
}
......@@ -149,10 +152,11 @@ pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigi
/// plurality.
///
/// May reasonably be used with `EnsureXcm`.
pub struct IsMajorityOfBody<Body>(PhantomData<Body>);
impl<Body: Get<BodyId>> Filter<MultiLocation> for IsMajorityOfBody<Body> {
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Filter<MultiLocation> for IsMajorityOfBody<Prefix, Body> {
fn filter(l: &MultiLocation) -> bool {
matches!(l, X1(Plurality { id, part }) if id == &Body::get() && part.is_majority())
let maybe_suffix = l.match_and_split(&Prefix::get());
matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
}
}
......@@ -180,3 +184,17 @@ impl<O: OriginTrait + From<Origin>, F: Filter<MultiLocation>> EnsureOrigin<O> fo
O::from(Origin::Xcm(MultiLocation::Null))
}
}
/// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of
/// this crate's `Origin::Xcm` value.
pub struct XcmPassthrough<Origin>(PhantomData<Origin>);
impl<
Origin: From<crate::Origin>,
> ConvertOrigin<Origin> for XcmPassthrough<Origin> {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
match (kind, origin) {
(OriginKind::Xcm, l) => Ok(crate::Origin::Xcm(l).into()),
(_, origin) => Err(origin),
}
}
}
......@@ -39,8 +39,10 @@ pub use traits::{Error, Result, SendXcm, ExecuteXcm, Outcome};
/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`.
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug)]
pub enum OriginKind {
/// Origin should just be the native origin for the sender. For Cumulus/Frame chains this is
/// the `Parachain` origin.
/// Origin should just be the native dispatch origin representation for the sender in the
/// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin
/// if coming from a chain, though there may be others if the `MultiLocation` XCM origin has a
/// primary/native dispatch origin form.
Native,
/// Origin should just be the standard account-based origin with the sovereign account of
......@@ -50,6 +52,11 @@ pub enum OriginKind {
/// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin.
/// This will not usually be an available option.
Superuser,
/// Origin should be interpreted as an XCM native origin and the `MultiLocation` should be
/// encoded directly in the dispatch origin unchanged. For Cumulus/Frame chains, this will be
/// the `pallet_xcm::Origin::Xcm` type.
Xcm,
}
/// Response data to a query.
......
......@@ -442,6 +442,20 @@ impl MultiLocation {
MultiLocationReverseIterator(self)
}
/// Ensures that self begins with `prefix` and that it has a single `Junction` item following. If
/// so, returns a reference to this `Junction` item.
pub fn match_and_split(&self, prefix: &MultiLocation) -> Option<&Junction> {
if prefix.len() + 1 != self.len() {
return None
}
for i in 0..prefix.len() {
if prefix.at(i) != self.at(i) {
return None
}
}
return self.at(prefix.len())
}
/// Mutates `self`, suffixing it with `new`. Returns `Err` in case of overflow.
pub fn push(&mut self, new: Junction) -> result::Result<(), ()> {
let mut n = MultiLocation::Null;
......
......@@ -72,6 +72,8 @@ pub enum Error {
NotWithdrawable,
/// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location.
LocationCannotHold,
/// The assets given to purchase weight is are insufficient for the weight desired.
TooExpensive,
}
impl From<()> for Error {
......
......@@ -16,6 +16,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", de
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
# Polkadot dependencies
polkadot-parachain = { path = "../../parachain", default-features = false }
......@@ -33,4 +34,5 @@ std = [
"sp-runtime/std",
"frame-support/std",
"polkadot-parachain/std",
"pallet-transaction-payment/std",
]
......@@ -46,7 +46,7 @@ mod fungibles_adapter;
pub use fungibles_adapter::FungiblesAdapter;
mod weight;
pub use weight::{FixedRateOfConcreteFungible, FixedWeightBounds};
pub use weight::{FixedRateOfConcreteFungible, FixedWeightBounds, UsingComponents};
mod matches_fungible;
pub use matches_fungible::{IsAbstract, IsConcrete};
......
......@@ -272,6 +272,6 @@ impl Config for TestConfig {
type LocationInverter = LocationInverter<TestAncestry>;
type Barrier = TestBarrier;
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall>;
type Trader = FixedRateOfConcreteFungible<WeightPrice>;
type Trader = FixedRateOfConcreteFungible<WeightPrice, ()>;
type ResponseHandler = TestResponseHandler;
}
......@@ -14,19 +14,22 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use sp_std::{result::Result, marker::PhantomData};
use sp_std::{result::Result, marker::PhantomData, convert::TryInto};
use parity_scale_codec::Decode;
use xcm::v0::{Xcm, Order, MultiAsset, MultiLocation};
use frame_support::{traits::Get, weights::{Weight, GetDispatchInfo}};
use xcm::v0::{Xcm, Order, MultiAsset, MultiLocation, Error};
use sp_runtime::traits::{Zero, Saturating, SaturatedConversion};
use frame_support::traits::{Get, OnUnbalanced as OnUnbalancedT, tokens::currency::Currency as CurrencyT};
use frame_support::weights::{Weight, GetDispatchInfo, WeightToFeePolynomial};
use xcm_executor::{Assets, traits::{WeightBounds, WeightTrader}};
pub struct FixedWeightBounds<T, C>(PhantomData<(T, C)>);
impl<T: Get<Weight>, C: Decode + GetDispatchInfo> WeightBounds<C> for FixedWeightBounds<T, C> {
fn shallow(message: &mut Xcm<C>) -> Result<Weight, ()> {
let min = match message {
Ok(match message {
Xcm::Transact { call, .. } => {
call.ensure_decoded()?.get_dispatch_info().weight + T::get()
}
Xcm::RelayedFrom { ref mut message, .. } => T::get() + Self::shallow(message.as_mut())?,
Xcm::WithdrawAsset { effects, .. }
| Xcm::ReserveAssetDeposit { effects, .. }
| Xcm::TeleportAsset { effects, .. } => {
......@@ -45,16 +48,16 @@ impl<T: Get<Weight>, C: Decode + GetDispatchInfo> WeightBounds<C> for FixedWeigh
T::get() + inner
}
_ => T::get(),
};
Ok(min)
})
}
fn deep(message: &mut Xcm<C>) -> Result<Weight, ()> {
let mut extra = 0;
match message {
Xcm::Transact { .. } => {}
Ok(match message {
Xcm::RelayedFrom { ref mut message, .. } => Self::deep(message.as_mut())?,
Xcm::WithdrawAsset { effects, .. }
| Xcm::ReserveAssetDeposit { effects, .. }
| Xcm::TeleportAsset { effects, .. } => {
| Xcm::TeleportAsset { effects, .. }
=> {
let mut extra = 0;
for effect in effects.iter_mut() {
match effect {
Order::BuyExecution { xcm, .. } => {
......@@ -65,34 +68,116 @@ impl<T: Get<Weight>, C: Decode + GetDispatchInfo> WeightBounds<C> for FixedWeigh
_ => {}
}
}
}
_ => {}
};
Ok(extra)
extra
},
_ => 0,
})
}
}
/// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but for a
/// `MultiAsset`. Sensible implementations will deposit the asset in some known treasury or block-author account.
pub trait TakeRevenue {
/// Do something with the given `revenue`, which is a single non-wildcard `MultiAsset`.
fn take_revenue(revenue: MultiAsset);
}
/// Null implementation just burns the revenue.
impl TakeRevenue for () {
fn take_revenue(_revenue: MultiAsset) {}
}
/// Simple fee calculator that requires payment in a single concrete fungible at a fixed rate.
///
/// The constant `Get` type parameter should be the concrete fungible ID and the amount of it required for
/// one second of weight.
pub struct FixedRateOfConcreteFungible<T>(Weight, PhantomData<T>);
impl<T: Get<(MultiLocation, u128)>> WeightTrader for FixedRateOfConcreteFungible<T> {
fn new() -> Self { Self(0, PhantomData) }
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, ()> {
pub struct FixedRateOfConcreteFungible<
T: Get<(MultiLocation, u128)>,
R: TakeRevenue,
>(Weight, u128, PhantomData<(T, R)>);
impl<T: Get<(MultiLocation, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfConcreteFungible<T, R> {
fn new() -> Self { Self(0, 0, PhantomData) }
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> {
let (id, units_per_second) = T::get();
let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128;
use frame_support::weights::constants::WEIGHT_PER_SECOND;
let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128);
let required = MultiAsset::ConcreteFungible { amount, id };
let (used, _) = payment.less(required).map_err(|_| ())?;
let (unused, _) = payment.less(required).map_err(|_| Error::TooExpensive)?;
self.0 = self.0.saturating_add(weight);
Ok(used)
self.1 = self.1.saturating_add(amount);
Ok(unused)
}
fn refund_weight(&mut self, weight: Weight) -> MultiAsset {
let weight = weight.min(self.0);
self.0 -= weight;
let (id, units_per_second) = T::get();
let weight = weight.min(self.0);
let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128;
self.0 -= weight;
self.1 = self.1.saturating_sub(amount);
let result = MultiAsset::ConcreteFungible { amount, id };
result
}
}
impl<T: Get<(MultiLocation, u128)>, R: TakeRevenue> Drop for FixedRateOfConcreteFungible<T, R> {
fn drop(&mut self) {
let revenue = MultiAsset::ConcreteFungible { amount: self.1, id: T::get().0 };
R::take_revenue(revenue);
}
}
/// Weight trader which uses the TransactionPayment pallet to set the right price for weight and then
/// places any weight bought into the right account.
pub struct UsingComponents<
WeightToFee: WeightToFeePolynomial<Balance=Currency::Balance>,
AssetId: Get<MultiLocation>,
AccountId,
Currency: CurrencyT<AccountId>,
OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
>(Weight, Currency::Balance, PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>);
impl<
WeightToFee: WeightToFeePolynomial<Balance=Currency::Balance>,
AssetId: Get<MultiLocation>,
AccountId,
Currency: CurrencyT<AccountId>,
OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
> WeightTrader for UsingComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> {
fn new() -> Self { Self(0, Zero::zero(), PhantomData) }
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error> {
let amount = WeightToFee::calc(&weight);
let required = MultiAsset::ConcreteFungible {
amount: amount.try_into().map_err(|_| Error::Overflow)?,
id: AssetId::get(),
};
let (unused, _) = payment.less(required).map_err(|_| Error::TooExpensive)?;
self.0 = self.0.saturating_add(weight);
self.1 = self.1.saturating_add(amount);
Ok(unused)
}
fn refund_weight(&mut self, weight: Weight) -> MultiAsset {
let weight = weight.min(self.0);
let amount = WeightToFee::calc(&weight);
self.0 -= weight;
self.1 = self.1.saturating_sub(amount);
let result = MultiAsset::ConcreteFungible {
amount: amount.saturated_into(),
id: AssetId::get(),
};
result
}
}
impl<
WeightToFee: WeightToFeePolynomial<Balance=Currency::Balance>,
AssetId: Get<MultiLocation>,
AccountId,
Currency: CurrencyT<AccountId>,
OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
> Drop for UsingComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> {
fn drop(&mut self) {
OnUnbalanced::on_unbalanced(Currency::issue(self.1));
}
}
......@@ -60,7 +60,9 @@ impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
return Outcome::Error(XcmError::WeightLimitReached);
}
let mut trader = Config::Trader::new();
match Self::do_execute_xcm(origin, true, message, &mut 0, Some(shallow_weight), &mut trader) {
let result = Self::do_execute_xcm(origin, true, message, &mut 0, Some(shallow_weight), &mut trader);
drop(trader);
match result {
Ok(surplus) => Outcome::Complete(maximum_weight.saturating_sub(surplus)),
// TODO: #2841 #REALWEIGHT We can do better than returning `maximum_weight` here, and we should otherwise
// we'll needlessly be disregarding block execution time.
......@@ -76,7 +78,9 @@ impl<Config: config::Config> XcmExecutor<Config> {
assets.into_assets_iter().collect::<Vec<_>>()
}
/// Execute the XCM and return any unexpected and unknowable surplus weight.
/// Execute the XCM and return the portion of weight of `shallow_weight + deep_weight` that `message` did not use.
///
/// NOTE: The amount returned must be less than `shallow_weight + deep_weight` of `message`.
fn do_execute_xcm(
origin: MultiLocation,
top_level: bool,
......@@ -95,8 +99,8 @@ impl<Config: config::Config> XcmExecutor<Config> {
.map_err(|()| XcmError::Barrier)?;
// The surplus weight, defined as the amount by which `shallow_weight` plus all nested
// `shallow_weight` values (ensuring no double-counting) is an overestimate of the actual weight
// consumed.
// `shallow_weight` values (ensuring no double-counting and also known as `deep_weight`) is an
// over-estimate of the actual weight consumed.
let mut total_surplus: Weight = 0;
let maybe_holding_effects = match (origin.clone(), message) {
......
......@@ -15,7 +15,7 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use sp_std::result::Result;
use xcm::v0::{Xcm, MultiAsset};
use xcm::v0::{Xcm, MultiAsset, Error};
use frame_support::weights::Weight;
use crate::Assets;
......@@ -46,14 +46,14 @@ pub trait WeightBounds<Call> {
}
/// Charge for weight in order to execute XCM.
pub trait WeightTrader {
pub trait WeightTrader: Sized {
/// Create a new trader instance.
fn new() -> Self;
/// Purchase execution weight credit in return for up to a given `fee`. If less of the fee is required
/// then the surplus is returned. If the `fee` cannot be used to pay for the `weight`, then an error is
/// returned.
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, ()>;
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, Error>;
/// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight was
/// purchased using `buy_weight`.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment