Skip to content

Commit

Permalink
Add basic XCM configuration (#29)
Browse files Browse the repository at this point in the history
* add basic XCM configuration

* and lost changes from Cargo.toml

* allow all calls through the bridge

* remove TODO and keep using root to dicspatch incoming calls

* improve matching

* Update runtime/src/xcm_config.rs

Co-authored-by: Branislav Kontur <bkontur@gmail.com>

* Update runtime/src/xcm_config.rs

Co-authored-by: Branislav Kontur <bkontur@gmail.com>

* Update runtime/src/xcm_config.rs

Co-authored-by: Branislav Kontur <bkontur@gmail.com>

* UniversalAliases + WithComputedOrigin

* added TODO

* Update runtime/src/xcm_config.rs

Co-authored-by: Branislav Kontur <bkontur@gmail.com>

* changed expected_message_from_kawabunga_passes_barrier to match real execution params

---------

Co-authored-by: Branislav Kontur <bkontur@gmail.com>
  • Loading branch information
svyatonik and bkontur authored Sep 28, 2023
1 parent 0582488 commit e8de205
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
log = { version = "0.4", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }

frame-executive = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
Expand Down Expand Up @@ -52,13 +53,19 @@ frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, g
pallet-transaction-storage = { version = "4.0.0-dev", default-features = false, path = "../pallets/transaction-storage" }
pallet-validator-set = { version = "1.0.0", default-features = false, path = "../pallets/validator-set" }

# XCM dependencies
xcm = { git = "https://github.com/paritytech/polkadot.git", default-features = false, branch = "release-v1.0.0" }
xcm-builder = { git = "https://github.com/paritytech/polkadot.git", default-features = false, branch = "release-v1.0.0" }
xcm-executor = { git = "https://github.com/paritytech/polkadot.git", default-features = false, branch = "release-v1.0.0" }

[build-dependencies]
substrate-wasm-builder = { version = "5.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v1.0.0" }

[features]
default = ["std"]
std = [
"codec/std",
"log/std",
"scale-info/std",

"frame-executive/std",
Expand Down Expand Up @@ -95,6 +102,10 @@ std = [
"pallet-transaction-storage/std",
"pallet-validator-set/std",

"xcm/std",
"xcm-builder/std",
"xcm-executor/std",

"substrate-wasm-builder",
]
runtime-benchmarks = [
Expand All @@ -113,6 +124,9 @@ runtime-benchmarks = [

"pallet-transaction-storage/runtime-benchmarks",
"pallet-validator-set/runtime-benchmarks",

"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
]
try-runtime = [
"frame-executive/try-runtime",
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub use pallet_timestamp::Call as TimestampCall;
pub use sp_runtime::BuildStorage;
pub use sp_runtime::{Perbill, Permill};

mod xcm_config;

/// An index to a block.
pub type BlockNumber = u32;

Expand Down
259 changes: 259 additions & 0 deletions runtime/src/xcm_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

//! XCM configuration for Polkadot Bulletin chain.

use crate::{AllPalletsWithSystem, RuntimeCall, RuntimeOrigin};

use codec::Decode;
use frame_support::{
ensure, match_types, parameter_types,
traits::{Contains, Everything, Nothing, ProcessMessageError},
weights::Weight,
};
use sp_core::ConstU32;
use xcm::latest::prelude::*;
use xcm_builder::{
CreateMatcher, FixedWeightBounds, MatchXcm, TrailingSetTopicAsId, WithComputedOrigin,
};
use xcm_executor::{
traits::{ConvertOrigin, ShouldExecute, WeightTrader, WithOriginFilter},
Assets,
};

// TODO [bridge]: change to actual value here + everywhere where Kawabunga is mentioned
/// Id of the Polkadot parachain that we are going to bridge with.
const KAWABUNGA_PARACHAIN_ID: u32 = 42;

parameter_types! {
// TODO [bridge]: how we are supposed to set it? Named? ByGenesis - if so, when? After generating
// chain spec?
/// The Polkadot Bulletin Chain network ID.
pub const ThisNetwork: NetworkId = NetworkId::ByGenesis([42u8; 32]);
/// Our location in the universe of consensus systems.
pub const UniversalLocation: InteriorMultiLocation = X1(GlobalConsensus(ThisNetwork::get()));

/// Location of the Kawabunga parachain, relative to this runtime.
pub KawabungaLocation: MultiLocation = MultiLocation::new(1, X2(
GlobalConsensus(Polkadot),
Parachain(KAWABUNGA_PARACHAIN_ID),
));

/// The amount of weight an XCM operation takes. This is a safe overestimate.
pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 0);
/// Maximum number of instructions in a single XCM fragment. A sanity check against weight
/// calculations getting too crazy.
pub const MaxInstructions: u32 = 100;
}

match_types! {
// Only contains Kawabunga parachain location.
pub type OnlyKawabungaLocation: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: X2(
GlobalConsensus(Polkadot),
Parachain(KAWABUNGA_PARACHAIN_ID),
) }
};

// Supported universal aliases.
pub type UniversalAliases: impl Contains<(MultiLocation, Junction)> = {
(
MultiLocation { parents: 0, interior: Here },
GlobalConsensus(Polkadot),
)
};
}

/// Kawabunga location converter to local root.
pub struct KawabungaParachainAsRoot;

impl ConvertOrigin<RuntimeOrigin> for KawabungaParachainAsRoot {
fn convert_origin(
origin: impl Into<MultiLocation>,
kind: OriginKind,
) -> Result<RuntimeOrigin, MultiLocation> {
let origin = origin.into();
log::trace!(
target: "xcm::origin_conversion",
"KawabungaParachainAsRoot origin: {:?}, kind: {:?}",
origin, kind,
);
match (kind, origin) {
(
OriginKind::Superuser,
MultiLocation {
parents: 1,
interior: X2(GlobalConsensus(Polkadot), Parachain(KAWABUNGA_PARACHAIN_ID)),
},
) => Ok(RuntimeOrigin::root()),
(_, origin) => Err(origin),
}
}
}

/// Weight trader that does nothing.
pub struct NoopTrader;

impl WeightTrader for NoopTrader {
fn new() -> Self {
NoopTrader
}

fn buy_weight(&mut self, _weight: Weight, _payment: Assets) -> Result<Assets, XcmError> {
Ok(Assets::new())
}

fn refund_weight(&mut self, _weight: Weight) -> Option<MultiAsset> {
None
}
}

/// Allows execution from `origin` if it is contained in `AllowedOrigin`
/// and if it is just a straight `Transact` which contains `AllowedCall`.
///
/// That's a 1:1 copy of corresponding Cumulus structure.
pub struct AllowUnpaidTransactsFrom<RuntimeCall, AllowedOrigin>(
sp_std::marker::PhantomData<(RuntimeCall, AllowedOrigin)>,
);
impl<RuntimeCall: Decode, AllowedOrigin: Contains<MultiLocation>> ShouldExecute
for AllowUnpaidTransactsFrom<RuntimeCall, AllowedOrigin>
{
fn should_execute<Call>(
origin: &MultiLocation,
instructions: &mut [Instruction<Call>],
max_weight: Weight,
_properties: &mut xcm_executor::traits::Properties,
) -> Result<(), ProcessMessageError> {
log::trace!(
target: "xcm::barriers",
"AllowUnpaidTransactsFrom origin: {:?}, instructions: {:?}, max_weight: {:?}, properties: {:?}",
origin, instructions, max_weight, _properties,
);

// we only allow from configured origins
ensure!(AllowedOrigin::contains(origin), ProcessMessageError::Unsupported);

// we expect an XCM program with single `Transact` call
instructions
.matcher()
.assert_remaining_insts(1)?
.match_next_inst(|inst| match inst {
Transact { origin_kind: OriginKind::Superuser, .. } => {
// we allow all calls through the bridge
Ok(())
},
_ => Err(ProcessMessageError::BadFormat),
})?;

Ok(())
}
}

/// The means that we convert an XCM origin `MultiLocation` into the runtime's `Origin` type for
/// local dispatch. This is a conversion function from an `OriginKind` type along with the
/// `MultiLocation` value and returns an `Origin` value or an error.
type LocalOriginConverter = (
// Currently we only accept XCM messages from Kawabunga and the origin for such messages
// is local root.
KawabungaParachainAsRoot,
);

// TODO [bridge]: configure to use bridge blob hauler here
/// Only bridged destination is supported.
pub type XcmRouter = ();

/// The barriers one of which must be passed for an XCM message to be executed.
pub type Barrier = TrailingSetTopicAsId<
WithComputedOrigin<
// We only allow unpaid execution from the Kawabunga parachain.
AllowUnpaidTransactsFrom<RuntimeCall, OnlyKawabungaLocation>,
UniversalLocation,
ConstU32<2>,
>,
>;

/// XCM executor configuration.
pub struct XcmConfig;

impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = XcmRouter;
type AssetTransactor = ();
type OriginConverter = LocalOriginConverter;
type IsReserve = ();
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
type Barrier = Barrier;
// TODO [bridge]: is it ok to use `FixedWeightBounds` here? We don't have the `pallet-xcm`
// and IIUC can't use XCM benchmarks because of that?
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
type Trader = NoopTrader;
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = ConstU32<0>;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = UniversalAliases;
type CallDispatcher = WithOriginFilter<Everything>;
type SafeCallFilter = Everything;
type Aliasers = Nothing;
}

#[cfg(test)]
mod tests {
use super::*;
use codec::Encode;
use xcm_executor::traits::Properties;

#[test]
fn expected_message_from_kawabunga_passes_barrier() {
// prepare message that we expect to come from the Polkadot BH
// (everything is relative to Polkadot BH)
let bridge_hub_universal_location = X2(GlobalConsensus(Polkadot), Parachain(1002));
let kawabunga_origin = MultiLocation::new(1, Parachain(KAWABUNGA_PARACHAIN_ID));
let universal_source =
bridge_hub_universal_location.within_global(kawabunga_origin).unwrap();
let (local_net, local_sub) = universal_source.split_global().unwrap();
let mut xcm: Xcm<RuntimeCall> = vec![
UniversalOrigin(GlobalConsensus(local_net)),
DescendOrigin(local_sub),
Transact {
origin_kind: OriginKind::Superuser,
require_weight_at_most: Weight::MAX,
call: RuntimeCall::System(frame_system::Call::remark { remark: vec![42] })
.encode()
.into(),
},
]
.into();

// ensure that it passes local XCM Barrier
assert_eq!(
Barrier::should_execute(
&Here.into(),
xcm.inner_mut(),
Weight::MAX,
&mut Properties { weight_credit: Weight::MAX, message_id: None },
),
Ok(())
);
}
}

0 comments on commit e8de205

Please sign in to comment.