diff --git a/Cargo.lock b/Cargo.lock index d70004d0c17e..5695b93716fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12253,9 +12253,11 @@ name = "xcm" version = "0.9.8" dependencies = [ "derivative", + "frame-support", "impl-trait-for-tuples", "log", "parity-scale-codec", + "sp-runtime", ] [[package]] diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 40dcba5b66bb..2902720511ba 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1288,6 +1288,7 @@ impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = Hrmp; type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 906c9d77d39d..f35a2e082f61 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -31,6 +31,7 @@ use sp_std::{ fmt, mem, prelude::*, }; +use xcm::v0::{Error as XcmError, HrmpChannelManagementHooks}; pub use pallet::*; @@ -467,6 +468,26 @@ pub mod pallet { } } +impl HrmpChannelManagementHooks for Pallet { + type HrmpCall = pallet::Call>; + fn hrmp_init_open_channel( + recipient: u32, + max_message_size: u32, + max_capacity: u32, + ) -> Result { + Ok(pallet::Call::hrmp_init_open_channel(recipient.into(), max_capacity, max_message_size)) + } + fn hrmp_accept_open_channel(sender: u32) -> Result { + Ok(pallet::Call::hrmp_accept_open_channel(sender.into())) + } + fn hrmp_close_channel(sender: u32, recipient: u32) -> Result { + Ok(pallet::Call::hrmp_close_channel(HrmpChannelId { + sender: sender.into(), + recipient: recipient.into(), + })) + } +} + #[cfg(feature = "std")] fn initialize_storage(preopen_hrmp_channels: &[(ParaId, ParaId, u32, u32)]) { let host_config = configuration::Pallet::::config(); diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index b007d04b60db..316c3ac5afb0 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -668,6 +668,7 @@ impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = Hrmp; type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 9583a3dbef27..d4a7231c7688 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -932,6 +932,7 @@ impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = Hrmp; type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = TrustedTeleporters; diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index adb604bad336..182b1c8626bf 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -10,10 +10,14 @@ impl-trait-for-tuples = "0.2.0" parity-scale-codec = { version = "2.0.0", default-features = false, features = [ "derive" ] } derivative = {version = "2.2.0", default-features = false, features = [ "use_core" ] } log = { version = "0.4.14", default-features = false } +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 } [features] default = ["std"] wasm-api = [] std = [ "parity-scale-codec/std", + "sp-runtime/std", + "frame-support/std", ] diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index bd8375dec3b8..4aaf8f33eb15 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -164,6 +164,7 @@ impl xcm_executor::Config for XcmConfig { type Call = Call; type XcmSender = TestSendXcm; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = (); type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = (); diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index d775f9acfb0b..82282dc1ad58 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -31,7 +31,7 @@ pub use junction::{BodyId, BodyPart, Junction, NetworkId}; pub use multi_asset::{AssetInstance, MultiAsset}; pub use multi_location::MultiLocation; pub use order::Order; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; +pub use traits::{Error, ExecuteXcm, HrmpChannelManagementHooks, Outcome, Result, SendXcm}; /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { @@ -262,6 +262,64 @@ pub enum Xcm { recipient: u32, }, + /// A message to propose opening a channel on the relay-chain between the + /// sender para to the recipient para. + /// + /// - `recipient`: The recipient in the to-be opened channel. + /// - `max_message_size`: The maximum size of a message proposed by the sender. + /// - `max_capacity`: The maximum number of messages that can be queued in the channel. + /// + /// Safety: The message should originate directly from the sender para. + /// + /// Kind: *Instruction*. + /// + /// Errors: + HrmpInitOpenChannel { + origin_type: OriginKind, + require_weight_at_most: u64, + #[codec(compact)] + recipient: u32, + #[codec(compact)] + max_message_size: u32, + #[codec(compact)] + max_capacity: u32, + }, + + /// A message to accept opening a channel on the relay-chain. + /// + /// - `sender`: The sender in the to-be opened channel. + /// + /// Safety: The message should originate directly from the recipient para. + /// + /// Kind: *Instruction*. + /// + /// Errors: + HrmpAcceptOpenChannel { + origin_type: OriginKind, + require_weight_at_most: u64, + #[codec(compact)] + sender: u32, + }, + + /// A message to close a channel on the relay-chain. + /// + /// - `sender`: The sender in the to-be closed channel. + /// - `recipient`: The recipient in the to-be closed channel. + /// + /// Safety: The message should originate directly from the sender or + /// recipient para (either member of the channel). + /// + /// Kind: *Instruction*. + /// + /// Errors: + HrmpCloseChannel { + origin_type: OriginKind, + require_weight_at_most: u64, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, + }, /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus /// location within the origin. /// @@ -314,6 +372,23 @@ impl Xcm { HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => HrmpChannelClosing { initiator, sender, recipient }, + HrmpInitOpenChannel { + origin_type, + require_weight_at_most, + recipient, + max_message_size, + max_capacity, + } => HrmpInitOpenChannel { + origin_type, + require_weight_at_most, + recipient, + max_message_size, + max_capacity, + }, + HrmpAcceptOpenChannel { origin_type, require_weight_at_most, sender } => + HrmpAcceptOpenChannel { origin_type, require_weight_at_most, sender }, + HrmpCloseChannel { origin_type, require_weight_at_most, sender, recipient } => + HrmpCloseChannel { origin_type, require_weight_at_most, sender, recipient }, Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call: call.into() }, RelayedFrom { who, message } => diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs index e1673fd5d990..e8b56eeb362f 100644 --- a/xcm/src/v0/traits.rs +++ b/xcm/src/v0/traits.rs @@ -18,6 +18,8 @@ use core::result; use parity_scale_codec::{Decode, Encode}; +use sp_runtime::traits::Dispatchable; +use frame_support::weights::GetDispatchInfo; use super::{MultiLocation, Xcm}; @@ -259,3 +261,36 @@ impl SendXcm for Tuple { Err(Error::CannotReachDestination(destination, message)) } } + +/// These hooks expose HRMP channel management functionality to enable parachains +/// to send messages that propose opening channels, accept opening channels, and +/// close open channels. +/// Parachains should configure the default `()` implementation which returns `Error::Undefined`. +/// Relay chains will use the implementation in the `hrmp` pallet. +pub trait HrmpChannelManagementHooks { + type HrmpCall: Dispatchable + GetDispatchInfo; + fn hrmp_init_open_channel( + recipient: u32, + max_message_size: u32, + max_capacity: u32, + ) -> result::Result; + fn hrmp_accept_open_channel(sender: u32) -> result::Result; + fn hrmp_close_channel(sender: u32, recipient: u32) -> result::Result; +} + +impl HrmpChannelManagementHooks for () { + type HrmpCall = (); + fn hrmp_init_open_channel( + _recipient: u32, + _max_message_size: u32, + _max_capacity: u32, + ) -> result::Result<(), Error> { + Err(().into()) + } + fn hrmp_accept_open_channel(_sender: u32) -> result::Result<(), Error> { + Err(().into()) + } + fn hrmp_close_channel(_sender: u32, _recipient: u32) -> result::Result<(), Error> { + Err(().into()) + } +} diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 86d54fa0fe91..86f1c1256eb6 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -269,6 +269,7 @@ impl Config for TestConfig { type Call = TestCall; type XcmSender = TestSendXcm; type AssetTransactor = TestAssetTransactor; + type HrmpChannelManager = (); type OriginConverter = TestOriginConverter; type IsReserve = TestIsReserve; type IsTeleporter = TestIsTeleporter; diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index ff39be56ef5e..b6d81236c865 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -22,7 +22,7 @@ use frame_support::{ dispatch::{Dispatchable, Parameter}, weights::{GetDispatchInfo, PostDispatchInfo}, }; -use xcm::v0::SendXcm; +use xcm::v0::{HrmpChannelManagementHooks, SendXcm}; /// The trait to parameterize the `XcmExecutor`. pub trait Config { @@ -35,6 +35,9 @@ pub trait Config { /// How to withdraw and deposit an asset. type AssetTransactor: TransactAsset; + /// Hooks to expose HRMP actions to XCM. + type HrmpChannelManager: HrmpChannelManagementHooks; + /// How to get a call origin from a `OriginKind` value. type OriginConverter: ConvertOrigin<::Origin>; diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index bb0b8eb604a8..1f7e3fb93713 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -23,8 +23,8 @@ use frame_support::{ }; use sp_std::{marker::PhantomData, prelude::*}; use xcm::v0::{ - Error as XcmError, ExecuteXcm, MultiAsset, MultiLocation, Order, Outcome, Response, SendXcm, - Xcm, + Error as XcmError, ExecuteXcm, HrmpChannelManagementHooks, MultiAsset, MultiLocation, Order, + Outcome, Response, SendXcm, Xcm, }; pub mod traits; @@ -234,6 +234,109 @@ impl XcmExecutor { // execution has taken. None }, + ( + origin, + Xcm::HrmpInitOpenChannel { + origin_type, + require_weight_at_most, + recipient, + max_message_size, + max_capacity, + }, + ) => { + let call = Config::HrmpChannelManager::hrmp_init_open_channel( + recipient, + max_message_size, + max_capacity, + )?; + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type) + .map_err(|_| XcmError::BadOrigin)?; + let weight = call.get_dispatch_info().weight; + ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired); + let actual_weight = match 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::HrmpAcceptOpenChannel { origin_type, require_weight_at_most, sender }, + ) => { + let call = Config::HrmpChannelManager::hrmp_accept_open_channel(sender)?; + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type) + .map_err(|_| XcmError::BadOrigin)?; + let weight = call.get_dispatch_info().weight; + ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired); + let actual_weight = match 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::HrmpCloseChannel { origin_type, require_weight_at_most, sender, recipient }, + ) => { + let call = Config::HrmpChannelManager::hrmp_close_channel(sender, recipient)?; + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type) + .map_err(|_| XcmError::BadOrigin)?; + let weight = call.get_dispatch_info().weight; + ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired); + let actual_weight = match 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 diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index f4ad471ff697..a3e2514b0fea 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -143,6 +143,7 @@ impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = (); type OriginConverter = XcmOriginToCallOrigin; type IsReserve = NativeAsset; type IsTeleporter = (); diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index c69f20d05eaf..cf4a300ecf5f 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -125,6 +125,7 @@ impl Config for XcmConfig { type Call = Call; type XcmSender = XcmRouter; type AssetTransactor = LocalAssetTransactor; + type HrmpChannelManager = (); type OriginConverter = LocalOriginConverter; type IsReserve = (); type IsTeleporter = ();