mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 11:41:02 +00:00
XCM revamp (#2836)
* Remove unused relaying XCM * Aggregate HRMP (XCMP/HMP) messages. Payloads for spambot. * Revert lock * Fix * Broken example * Introduce fee payment mechanics into XCM. * Weight limitations on XCM execution * Mock environment for tests and the first test * Tests for XCM and a few refactors. * Remove code that's not ready * Fix for an XCM and an additional test * Query response system * XCMP message dispatch system reimagining - Moved most of the logic into xcm-handler pallet - Altered the outgoing XCMP API from push to pull - Changed underlying outgoing queue data structures to avoid multi-page read/writes - Introduced queuing for incoming messages - Introduced signal messages as a flow-control sub-stream - Introduced flow-control with basic threshold back-pressure - Introduced overall weight limitation on messages executed - Additonal alterations to XCM APIs for the new system * Some build fixes * Remove the Encode bounds sprayed around * More faff * Fix bounds amek use latest scale codec. * remove println * fixes * Fix XcmExecutor Tests * Fix XCM bounds using derivative crate * Refactor names of XcmGeneric &c into Xcm * Repot the xcm-executor into xcm-builder * Docs * Docs * Fixes * Update xcm/src/lib.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Fixes * Docs * Update runtime/parachains/src/ump.rs Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Docs * Fixes * Fixes * Fixes * Docs * Fixes * Fixes * Introduce transfer_asset specialisation. * Fixes * Fixes Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -16,120 +16,197 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use sp_std::{prelude::*, marker::PhantomData, convert::TryInto};
|
||||
use frame_support::{ensure, dispatch::Dispatchable};
|
||||
use parity_scale_codec::Decode;
|
||||
use sp_std::{prelude::*, marker::PhantomData};
|
||||
use frame_support::{
|
||||
ensure, weights::GetDispatchInfo,
|
||||
dispatch::{Weight, Dispatchable}
|
||||
};
|
||||
use xcm::v0::{
|
||||
Xcm, Order, ExecuteXcm, SendXcm, Error as XcmError, Result as XcmResult,
|
||||
MultiLocation, MultiAsset, Junction,
|
||||
ExecuteXcm, SendXcm, Error as XcmError, Outcome,
|
||||
MultiLocation, MultiAsset, Xcm, Order, Response,
|
||||
};
|
||||
|
||||
pub mod traits;
|
||||
mod assets;
|
||||
mod config;
|
||||
use traits::{
|
||||
TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation, WeightBounds, WeightTrader, ShouldExecute,
|
||||
OnResponse
|
||||
};
|
||||
|
||||
use traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation};
|
||||
mod assets;
|
||||
pub use assets::{Assets, AssetId};
|
||||
mod config;
|
||||
pub use config::Config;
|
||||
|
||||
pub struct XcmExecutor<Config>(PhantomData<Config>);
|
||||
|
||||
impl<Config: config::Config> ExecuteXcm for XcmExecutor<Config> {
|
||||
fn execute_xcm(origin: MultiLocation, msg: Xcm) -> XcmResult {
|
||||
let (mut holding, effects) = match (origin.clone(), msg) {
|
||||
(origin, Xcm::RelayedFrom { superorigin, inner }) => {
|
||||
// We ensure that it doesn't contain any `Parent` Junctions which would imply a privilege escalation.
|
||||
let mut new_origin = origin;
|
||||
for j in superorigin.into_iter() {
|
||||
ensure!(j.is_sub_consensus(), XcmError::EscalationOfPrivilege);
|
||||
new_origin.push(j).map_err(|_| XcmError::MultiLocationFull)?;
|
||||
}
|
||||
return Self::execute_xcm(
|
||||
new_origin,
|
||||
(*inner).try_into().map_err(|_| XcmError::UnhandledXcmVersion)?
|
||||
)
|
||||
}
|
||||
(origin, Xcm::WithdrawAsset { assets, effects }) => {
|
||||
// Take `assets` from the origin account (on-chain) and place in holding.
|
||||
let mut holding = Assets::default();
|
||||
for asset in assets {
|
||||
let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?;
|
||||
holding.saturating_subsume(withdrawn);
|
||||
}
|
||||
(holding, effects)
|
||||
}
|
||||
(origin, Xcm::ReserveAssetDeposit { assets, effects }) => {
|
||||
// check whether we trust origin to be our reserve location for this asset.
|
||||
if assets.iter().all(|asset| Config::IsReserve::filter_asset_location(asset, &origin)) {
|
||||
// We only trust the origin to send us assets that they identify as their
|
||||
// sovereign assets.
|
||||
(Assets::from(assets), effects)
|
||||
} else {
|
||||
Err(XcmError::UntrustedReserveLocation)?
|
||||
}
|
||||
}
|
||||
(origin, Xcm::TeleportAsset { assets, effects }) => {
|
||||
// check whether we trust origin to teleport this asset to us via config trait.
|
||||
// TODO: should de-wildcard `assets` before passing in.
|
||||
log::debug!(target: "runtime::xcm-executor", "Teleport from {:?}", origin);
|
||||
if assets.iter().all(|asset| Config::IsTeleporter::filter_asset_location(asset, &origin)) {
|
||||
// We only trust the origin to send us assets that they identify as their
|
||||
// sovereign assets.
|
||||
(Assets::from(assets), effects)
|
||||
} else {
|
||||
Err(XcmError::UntrustedTeleportLocation)?
|
||||
}
|
||||
}
|
||||
(origin, Xcm::Transact { origin_type, call }) => {
|
||||
// We assume that the Relay-chain is allowed to use transact on this parachain.
|
||||
|
||||
// TODO: Weight fees should be paid.
|
||||
|
||||
// TODO: allow this to be configurable in the trait.
|
||||
// TODO: allow the trait to issue filters for the relay-chain
|
||||
let message_call = Config::Call::decode(&mut &call[..]).map_err(|_| XcmError::FailedToDecode)?;
|
||||
let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type)
|
||||
.map_err(|_| XcmError::BadOrigin)?;
|
||||
let _ok = message_call.dispatch(dispatch_origin).is_ok();
|
||||
// Not much to do with the result as it is. It's up to the parachain to ensure that the
|
||||
// message makes sense.
|
||||
return Ok(());
|
||||
}
|
||||
(origin, Xcm::RelayTo { dest: MultiLocation::X1(Junction::Parachain { id }), inner }) => {
|
||||
let msg = Xcm::RelayedFrom { superorigin: origin, inner }.into();
|
||||
return Config::XcmSender::send_xcm(Junction::Parachain { id }.into(), msg)
|
||||
},
|
||||
_ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message.
|
||||
impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
|
||||
fn execute_xcm(origin: MultiLocation, message: Xcm<Config::Call>, weight_limit: Weight) -> Outcome {
|
||||
// TODO: #2841 #HARDENXCM We should identify recursive bombs here and bail.
|
||||
let mut message = Xcm::<Config::Call>::from(message);
|
||||
let shallow_weight = match Config::Weigher::shallow(&mut message) {
|
||||
Ok(x) => x,
|
||||
Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
|
||||
};
|
||||
|
||||
// TODO: stuff that should happen after holding is populated but before effects,
|
||||
// including depositing fees for effects from holding account.
|
||||
|
||||
for effect in effects.into_iter() {
|
||||
let _ = Self::execute_effects(&origin, &mut holding, effect)?;
|
||||
let deep_weight = match Config::Weigher::deep(&mut message) {
|
||||
Ok(x) => x,
|
||||
Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
|
||||
};
|
||||
let maximum_weight = match shallow_weight.checked_add(deep_weight) {
|
||||
Some(x) => x,
|
||||
None => return Outcome::Error(XcmError::WeightLimitReached),
|
||||
};
|
||||
if maximum_weight > weight_limit {
|
||||
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) {
|
||||
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.
|
||||
Err(e) => Outcome::Incomplete(maximum_weight, e),
|
||||
}
|
||||
|
||||
// TODO: stuff that should happen after effects including refunding unused fees.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Config: config::Config> XcmExecutor<Config> {
|
||||
fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec<MultiAsset> {
|
||||
let inv_dest = Config::LocationInverter::invert_location(&dest);
|
||||
assets.reanchor(&inv_dest);
|
||||
assets.prepend_location(&inv_dest);
|
||||
assets.into_assets_iter().collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn execute_effects(_origin: &MultiLocation, holding: &mut Assets, effect: Order) -> XcmResult {
|
||||
/// Execute the XCM and return any unexpected and unknowable surplus weight.
|
||||
fn do_execute_xcm(
|
||||
origin: MultiLocation,
|
||||
top_level: bool,
|
||||
mut message: Xcm<Config::Call>,
|
||||
weight_credit: &mut Weight,
|
||||
maybe_shallow_weight: Option<Weight>,
|
||||
trader: &mut Config::Trader,
|
||||
) -> Result<Weight, XcmError> {
|
||||
// This is the weight of everything that cannot be paid for. This basically means all computation
|
||||
// except any XCM which is behind an Order::BuyExecution.
|
||||
let shallow_weight = maybe_shallow_weight
|
||||
.or_else(|| Config::Weigher::shallow(&mut message).ok())
|
||||
.ok_or(XcmError::WeightNotComputable)?;
|
||||
|
||||
Config::Barrier::should_execute(&origin, top_level, &message, shallow_weight, weight_credit)
|
||||
.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.
|
||||
let mut total_surplus: Weight = 0;
|
||||
|
||||
let maybe_holding_effects = match (origin.clone(), message) {
|
||||
(origin, Xcm::WithdrawAsset { assets, effects }) => {
|
||||
// Take `assets` from the origin account (on-chain) and place in holding.
|
||||
let mut holding = Assets::default();
|
||||
for asset in assets {
|
||||
ensure!(!asset.is_wildcard(), XcmError::Wildcard);
|
||||
let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?;
|
||||
holding.saturating_subsume_all(withdrawn);
|
||||
}
|
||||
Some((holding, effects))
|
||||
}
|
||||
(origin, Xcm::ReserveAssetDeposit { assets, effects }) => {
|
||||
// check whether we trust origin to be our reserve location for this asset.
|
||||
for asset in assets.iter() {
|
||||
ensure!(!asset.is_wildcard(), XcmError::Wildcard);
|
||||
// We only trust the origin to send us assets that they identify as their
|
||||
// sovereign assets.
|
||||
ensure!(Config::IsReserve::filter_asset_location(asset, &origin), XcmError::UntrustedReserveLocation);
|
||||
}
|
||||
Some((Assets::from(assets), effects))
|
||||
}
|
||||
(origin, Xcm::TransferAsset { assets, dest }) => {
|
||||
// Take `assets` from the origin account (on-chain) and place into dest account.
|
||||
for asset in assets {
|
||||
ensure!(!asset.is_wildcard(), XcmError::Wildcard);
|
||||
Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
(origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => {
|
||||
// Take `assets` from the origin account (on-chain) and place into dest account.
|
||||
let inv_dest = Config::LocationInverter::invert_location(&dest);
|
||||
for asset in assets.iter_mut() {
|
||||
ensure!(!asset.is_wildcard(), XcmError::Wildcard);
|
||||
Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?;
|
||||
asset.reanchor(&inv_dest)?;
|
||||
}
|
||||
Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?;
|
||||
None
|
||||
}
|
||||
(origin, Xcm::TeleportAsset { assets, effects }) => {
|
||||
// check whether we trust origin to teleport this asset to us via config trait.
|
||||
for asset in assets.iter() {
|
||||
ensure!(!asset.is_wildcard(), XcmError::Wildcard);
|
||||
// We only trust the origin to send us assets that they identify as their
|
||||
// sovereign assets.
|
||||
ensure!(Config::IsTeleporter::filter_asset_location(asset, &origin), XcmError::UntrustedTeleportLocation);
|
||||
}
|
||||
Some((Assets::from(assets), effects))
|
||||
}
|
||||
(origin, Xcm::Transact { origin_type, require_weight_at_most, mut call }) => {
|
||||
// We assume that the Relay-chain is allowed to use transact on this parachain.
|
||||
|
||||
// TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain
|
||||
let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;
|
||||
let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type)
|
||||
.map_err(|_| XcmError::BadOrigin)?;
|
||||
let weight = message_call.get_dispatch_info().weight;
|
||||
ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired);
|
||||
let actual_weight = match message_call.dispatch(dispatch_origin) {
|
||||
Ok(post_info) => post_info.actual_weight,
|
||||
Err(error_and_info) => {
|
||||
// Not much to do with the result as it is. It's up to the parachain to ensure that the
|
||||
// message makes sense.
|
||||
error_and_info.post_info.actual_weight
|
||||
}
|
||||
}.unwrap_or(weight);
|
||||
let surplus = weight.saturating_sub(actual_weight);
|
||||
// Credit any surplus weight that we bought. This should be safe since it's work we
|
||||
// didn't realise that we didn't have to do.
|
||||
// It works because we assume that the `Config::Weigher` will always count the `call`'s
|
||||
// `get_dispatch_info` weight into its `shallow` estimate.
|
||||
*weight_credit = weight_credit.saturating_add(surplus);
|
||||
// Do the same for the total surplus, which is reported to the caller and eventually makes its way
|
||||
// back up the stack to be subtracted from the deep-weight.
|
||||
total_surplus = total_surplus.saturating_add(surplus);
|
||||
// Return the overestimated amount so we can adjust our expectations on how much this entire
|
||||
// execution has taken.
|
||||
None
|
||||
}
|
||||
(origin, Xcm::QueryResponse { query_id, response }) => {
|
||||
Config::ResponseHandler::on_response(origin, query_id, response);
|
||||
None
|
||||
}
|
||||
_ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message.
|
||||
};
|
||||
|
||||
if let Some((mut holding, effects)) = maybe_holding_effects {
|
||||
for effect in effects.into_iter() {
|
||||
total_surplus += Self::execute_effects(&origin, &mut holding, effect, trader)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(total_surplus)
|
||||
}
|
||||
|
||||
fn execute_effects(
|
||||
origin: &MultiLocation,
|
||||
holding: &mut Assets,
|
||||
effect: Order<Config::Call>,
|
||||
trader: &mut Config::Trader,
|
||||
) -> Result<Weight, XcmError> {
|
||||
let mut total_surplus = 0;
|
||||
match effect {
|
||||
Order::DepositAsset { assets, dest } => {
|
||||
let deposited = holding.saturating_take(assets);
|
||||
for asset in deposited.into_assets_iter() {
|
||||
Config::AssetTransactor::deposit_asset(&asset, &dest)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
Order::DepositReserveAsset { assets, dest, effects } => {
|
||||
let deposited = holding.saturating_take(assets);
|
||||
@@ -137,21 +214,39 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
Config::AssetTransactor::deposit_asset(&asset, &dest)?;
|
||||
}
|
||||
let assets = Self::reanchored(deposited, &dest);
|
||||
Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })
|
||||
Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?;
|
||||
},
|
||||
Order::InitiateReserveWithdraw { assets, reserve, effects} => {
|
||||
let assets = Self::reanchored(holding.saturating_take(assets), &reserve);
|
||||
Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })
|
||||
Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })?;
|
||||
}
|
||||
Order::InitiateTeleport { assets, dest, effects} => {
|
||||
let assets = Self::reanchored(holding.saturating_take(assets), &dest);
|
||||
Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects })
|
||||
Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects })?;
|
||||
}
|
||||
Order::QueryHolding { query_id, dest, assets } => {
|
||||
let assets = Self::reanchored(holding.min(assets.iter()), &dest);
|
||||
Config::XcmSender::send_xcm(dest, Xcm::Balances { query_id, assets })
|
||||
Config::XcmSender::send_xcm(dest, Xcm::QueryResponse { query_id, response: Response::Assets(assets) })?;
|
||||
}
|
||||
_ => Err(XcmError::UnhandledEffect)?,
|
||||
Order::BuyExecution { fees, weight, debt, halt_on_error, xcm } => {
|
||||
// pay for `weight` using up to `fees` of the holding account.
|
||||
let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?);
|
||||
let max_fee = holding.try_take(fees).map_err(|()| XcmError::NotHoldingFees)?;
|
||||
let unspent = trader.buy_weight(purchasing_weight, max_fee)?;
|
||||
holding.saturating_subsume_all(unspent);
|
||||
|
||||
let mut remaining_weight = weight;
|
||||
for message in xcm.into_iter() {
|
||||
match Self::do_execute_xcm(origin.clone(), false, message, &mut remaining_weight, None, trader) {
|
||||
Err(e) if halt_on_error => return Err(e),
|
||||
Err(_) => {}
|
||||
Ok(surplus) => { total_surplus += surplus }
|
||||
}
|
||||
}
|
||||
holding.saturating_subsume(trader.refund_weight(remaining_weight));
|
||||
}
|
||||
_ => return Err(XcmError::UnhandledEffect)?,
|
||||
}
|
||||
Ok(total_surplus)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user