diff --git a/Cargo.toml b/Cargo.toml index 3eb8697f..8be89924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,11 @@ members = [ "frame/pallet-xvm", "frame/xc-asset-config", "frame/contracts-migration", + "frame/pallet-xcm-transactor", + "frame/pallet-xcm-transactor/primitives", + "frame/pallet-xcm-transactor/xcm-simulator", + "frame/pallet-xcm-transactor/ink-sdk", + # "frame/pallet-xcm-transactor/contract-examples/basic-flip", "primitives/xcm", "precompiles/assets-erc20", "precompiles/dapps-staking", @@ -120,12 +125,17 @@ pallet-authorship = { git = "https://github.com/paritytech/substrate", branch = pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } pallet-assets = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } +pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } # Polkadot # (wasm) +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.39", default-features = false } xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } # (native) diff --git a/frame/pallet-xcm-transactor/Cargo.toml b/frame/pallet-xcm-transactor/Cargo.toml new file mode 100644 index 00000000..cb654866 --- /dev/null +++ b/frame/pallet-xcm-transactor/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "pallet-xcm-transactor" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = { workspace = true } +num_enum = { version = "0.6", default-features = false } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +# substrate core +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# frame dependencies +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +# pallets +pallet-contracts = { workspace = true } +pallet-xcm = { workspace = true } + +# xcm +xcm = { workspace = true } +xcm-executor = { workspace = true } + +# types +xcm-ce-primitives = { path = "./primitives", default-features = false } + +[dev-dependencies] +pallet-balances = { workspace = true } +pallet-insecure-randomness-collective-flip = { workspace = true } +pallet-timestamp = { workspace = true } +polkadot-core-primitives = { workspace = true } +polkadot-parachain = { workspace = true } +polkadot-runtime-parachains = { workspace = true } +xcm-builder = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "num_enum/std", + "parity-scale-codec/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking?/std", + "xcm/std", + "xcm-executor/std", + "pallet-xcm/std", + "pallet-contracts/std", + "xcm-ce-primitives/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "pallet-contracts/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "pallet-xcm/try-runtime", + "pallet-contracts/try-runtime", +] diff --git a/frame/pallet-xcm-transactor/README.md b/frame/pallet-xcm-transactor/README.md new file mode 100644 index 00000000..92e159d0 --- /dev/null +++ b/frame/pallet-xcm-transactor/README.md @@ -0,0 +1,75 @@ +# XCM CE + Companion Pallet +_XCM Chain extension with callback functionality_ + +> This is WIP draft implementation with a lot of pending TODOs and security considerations still to be addressed. + +## Chain Extension + +The CE has following commands and [SDK for ink! contracts](frame/pallet-xcm-transactor/ink-sdk). +```rust +pub enum Command { + /// Returns the weight for given XCM and saves it (in CE, per-call scratch buffer) for + /// execution + PrepareExecute = 0, + /// Execute the XCM that was prepared earlier + Execute = 1, + /// Returns the fee required to send XCM and saves it for sending + ValidateSend = 2, + /// Send the validated XCM + Send = 3, + /// Register the new query + NewQuery = 4, + /// Take the response for query if available, in case of no callback query + TakeResponse = 5, + /// Get the pallet account id which will be the caller of contract callback + PalletAccountId = 6, +} + +``` + +## Callback Design +The callback design make use of `pallet_xcm`'s `OnResponse` handler which has capability to notify a dispatch on a XCM response (if notify query is registered). + +For us that dispatch is companion pallet's `on_callback_received` which will route the xcm response (`Response` enum) back to contract via a `bare_call` (if wasm contract) + +![image](https://user-images.githubusercontent.com/17181457/236989729-acf5ac13-4abe-4340-bcdc-6ca22fb5d411.png) + + +## Structure +``` +├── pallet-xcm-transactor +│ ├── contract-examples # contract examples using XCM CE, some of which are used as fixtures in tests +│ ├── ink-sdk # ink helper methods to build CE calls and export types +│ ├── primitives # common types to share with pallet and CE +│ ├── src # companion pallet, for callback support +│ └── xcm-simulator # xcm simulator, for testing XCM CE +``` + + +## Local testing +All the test scenarios are done inside XCM Simulator - [here](frame/pallet-xcm-transactor/xcm-simulator/src/lib.rs) + +### Run tests +- cd into xcm simulator directory + ``` + cd frame/pallet-xcm-transactor/xcm-simulator + ``` +- `cargo test` - it will take a while for first time since it needs to build the contracts too + +To print the XCM logs, use the below command +``` +RUST_LOG="xcm=trace" cargo test -- --nocapture --test-threads=1 +``` + +### To add new contract to fixtures +1. Create the contract inside [`contract-examples`](frame/pallet-xcm-transactor/contract-examples) directory +2. Add the contract in [`build.rs`](frame/pallet-xcm-transactor/xcm-simulator/build.rs) of simulator tests so that contract will be compiled and copied to fixtures dir before test runs. + ``` + build_contract( + &fixtures_dir, + &contracts_dir.join("YOUR_CONTRACT"), + "YOUR_CONTRACT", + ); + ``` + +See the existing tests to know how fixtures are used. diff --git a/frame/pallet-xcm-transactor/contract-examples/basic-flip/.gitignore b/frame/pallet-xcm-transactor/contract-examples/basic-flip/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/frame/pallet-xcm-transactor/contract-examples/basic-flip/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/frame/pallet-xcm-transactor/contract-examples/basic-flip/Cargo.toml b/frame/pallet-xcm-transactor/contract-examples/basic-flip/Cargo.toml new file mode 100755 index 00000000..08905407 --- /dev/null +++ b/frame/pallet-xcm-transactor/contract-examples/basic-flip/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "basic_flip" +version = "0.1.0" +authors = ["Ashutosh Varma "] +edition = "2021" + +[dependencies] +ink = { version = "~4.2.0", default-features = false } + +num_enum = { version = "0.6", default-features = false } +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +xcm-ce-sdk = { path = "../../ink-sdk", default-features = false } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "num_enum/std", + "scale/std", + "scale-info/std", + "xcm/std", + "xcm-ce-sdk/std", +] +ink-as-dependency = [] + +[profile.release] +overflow-checks = false + +[workspace] diff --git a/frame/pallet-xcm-transactor/contract-examples/basic-flip/lib.rs b/frame/pallet-xcm-transactor/contract-examples/basic-flip/lib.rs new file mode 100755 index 00000000..0730e64e --- /dev/null +++ b/frame/pallet-xcm-transactor/contract-examples/basic-flip/lib.rs @@ -0,0 +1,121 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract] +mod contracts { + use ink::{env::DefaultEnvironment, storage::Mapping}; + use xcm::{latest::Weight, prelude::*}; + pub use xcm_ce_sdk::{ + types::{QueryConfig, ValidateSendInput}, + Error as XcmCEError, XcmExtension as _XcmExtension, + }; + + type XcmExtension = _XcmExtension; + + #[ink(storage)] + #[derive(Default)] + pub struct Contracts { + value: bool, + expecting_query: Mapping, + } + + impl Contracts { + #[ink(constructor, selector = 0xFFFFFFFF)] + pub fn default() -> Self { + Self { + ..Default::default() + } + } + + #[ink(message, selector = 0x11111111)] + pub fn execute(&mut self, xcm: VersionedXcm<()>) -> Result { + let weight = XcmExtension::prepare_execute(xcm)?; + ink::env::debug_println!("[1/2] Prepared XCM"); + + XcmExtension::execute()?; + ink::env::debug_println!("[2/2] Execute XCM"); + + Ok(weight) + } + + #[ink(message, selector = 0x22222222)] + pub fn send( + &mut self, + input: ValidateSendInput, + ) -> Result { + let fees = XcmExtension::validate_send(input)?; + ink::env::debug_println!("[1/2] Validate Send XCM"); + + XcmExtension::send()?; + ink::env::debug_println!("[2/2] Send XCM"); + + Ok(fees) + } + + #[ink(message, selector = 0x33333333)] + pub fn query( + &mut self, + config: QueryConfig, + dest: VersionedMultiLocation, + ) -> Result { + ink::env::debug_println!("[1/3] Registering Query..., {config:?}"); + let query_id = XcmExtension::new_query(config, dest)?; + ink::env::debug_println!("[2/3] Registered Query"); + + self.expecting_query.insert(query_id, &true); + ink::env::debug_println!("[3/3] Save Query"); + + Ok(query_id) + } + + #[ink(message, selector = 0x44444444)] + pub fn poll_response(&mut self, query_id: QueryId) -> Result { + ink::env::debug_println!("[1/1] Response Recieved for QueryId - {query_id}"); + XcmExtension::take_response(query_id) + } + + #[ink(message, selector = 0x55555555)] + pub fn handle_response( + &mut self, + query_id: QueryId, + _responder: MultiLocation, + _response: Response, + ) { + ink::env::debug_println!("[1/1] Response Recieved for QueryId - {query_id}"); + assert!(XcmExtension::pallet_account_id() == self.env().caller()); + match self.expecting_query.get(query_id) { + Some(expecting) if expecting == true => { + // NOTE: do not delete storage, because storage deposit + // refund will fail. + // self.expecting_query.remove(query_id); + self.value = !self.value; + } + _ => { + panic!("Not expecting response"); + } + } + } + + #[ink(message, selector = 0x66666666)] + pub fn get(&self) -> bool { + self.value + } + } +} diff --git a/frame/pallet-xcm-transactor/ink-sdk/.gitignore b/frame/pallet-xcm-transactor/ink-sdk/.gitignore new file mode 100755 index 00000000..8de8f877 --- /dev/null +++ b/frame/pallet-xcm-transactor/ink-sdk/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/frame/pallet-xcm-transactor/ink-sdk/Cargo.toml b/frame/pallet-xcm-transactor/ink-sdk/Cargo.toml new file mode 100755 index 00000000..5db0b2bd --- /dev/null +++ b/frame/pallet-xcm-transactor/ink-sdk/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "xcm-ce-sdk" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +ink = { version = "~4.2.0", default-features = false } + +num_enum = { version = "0.6", default-features = false } +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.39", default-features = false } +xcm-ce-primitives = { path = "../primitives", default-features = false } + +[lib] +path = "lib.rs" + +# [profile.release] +# overflow-checks = false # Disable integer overflow checks. + +[features] +default = ["std"] +std = [ + "ink/std", + "num_enum/std", + "scale/std", + "scale-info/std", + "xcm/std", + "xcm-ce-primitives/std", +] +ink-as-dependency = [] diff --git a/frame/pallet-xcm-transactor/ink-sdk/README.md b/frame/pallet-xcm-transactor/ink-sdk/README.md new file mode 100644 index 00000000..a5ff1353 --- /dev/null +++ b/frame/pallet-xcm-transactor/ink-sdk/README.md @@ -0,0 +1,2 @@ +# xcm-ce-sdk +SDK for building chain extensions call for XCM CE and exports primitive types. diff --git a/frame/pallet-xcm-transactor/ink-sdk/lib.rs b/frame/pallet-xcm-transactor/ink-sdk/lib.rs new file mode 100755 index 00000000..e4c9aab9 --- /dev/null +++ b/frame/pallet-xcm-transactor/ink-sdk/lib.rs @@ -0,0 +1,134 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; +use ink::env::{chain_extension::FromStatusCode, DefaultEnvironment, Environment}; +use scale::{Decode, Encode}; +use xcm::{latest::Weight, prelude::*}; +use xcm_ce_primitives::{ + create_error_enum, Command, QueryConfig, ValidateSendInput, XCM_EXTENSION_ID, +}; + +/// Re-export everything from xcm-ce-primitves +pub mod types { + pub use xcm_ce_primitives::*; +} + +create_error_enum!(pub Error); + +impl FromStatusCode for Error { + fn from_status_code(status_code: u32) -> Result<(), Self> { + match status_code { + 0 => Ok(()), + code => Err(code.into()), + } + } +} + +/// XCM Chain Extension Interface +pub struct XcmExtension(PhantomData); + +impl XcmExtension { + const fn get_func_id(idx: u16) -> u32 { + ((ID as u32) << 16) + (idx as u32) + } + + pub fn prepare_execute(xcm: VersionedXcm<()>) -> Result { + let func_id: u32 = Self::get_func_id(Command::PrepareExecute.into()); + + // fn(VersionedXcm<()>) -> Result + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::>() + .output::() + .handle_error_code::() + .call(&(xcm)) + } + + pub fn execute() -> Result<(), Error> { + let func_id: u32 = Self::get_func_id(Command::Execute.into()); + + // fn() -> Result<(Weight), Error> + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::<()>() + .output::<(), false>() + .handle_error_code::() + .call(&()) + } + + pub fn validate_send(input: ValidateSendInput) -> Result { + let func_id: u32 = Self::get_func_id(Command::ValidateSend.into()); + + // fn(ValidateSendInput) -> Result + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::() + .output::() + .handle_error_code::() + .call(&(input)) + } + + pub fn send() -> Result<(), Error> { + let func_id: u32 = Self::get_func_id(Command::Send.into()); + + // fn() -> Result<(), Error> + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::<()>() + .output::<(), false>() + .handle_error_code::() + .call(&()) + } + + pub fn new_query( + config: QueryConfig, + dest: VersionedMultiLocation, + ) -> Result { + let func_id: u32 = Self::get_func_id(Command::NewQuery.into()); + + // fn(QueryConfig, VersionedMultiLocation) -> Result + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::<( + QueryConfig, + VersionedMultiLocation, + )>() + .output::() + .handle_error_code::() + .call(&(config, dest)) + } + + pub fn take_response(query_id: QueryId) -> Result { + let func_id: u32 = Self::get_func_id(Command::TakeResponse.into()); + + // fn(QueryId) -> Result + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::() + .output::() + .handle_error_code::() + .call(&(query_id)) + } + + pub fn pallet_account_id() -> E::AccountId { + let func_id = Self::get_func_id(Command::PalletAccountId.into()); + + ::ink::env::chain_extension::ChainExtensionMethod::build(func_id) + .input::<()>() + .output::() + .ignore_error_code() + .call(&()) + } +} diff --git a/frame/pallet-xcm-transactor/primitives/Cargo.toml b/frame/pallet-xcm-transactor/primitives/Cargo.toml new file mode 100644 index 00000000..63b44746 --- /dev/null +++ b/frame/pallet-xcm-transactor/primitives/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "xcm-ce-primitives" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num_enum = { version = "0.6", default-features = false } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +#substarte +sp-core = { workspace = true } +sp-runtime = { workspace = true } + +# xcm +xcm = { workspace = true } + +[features] +default = ["std"] +std = [ + "num_enum/std", + "parity-scale-codec/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "xcm/std", +] diff --git a/frame/pallet-xcm-transactor/primitives/src/lib.rs b/frame/pallet-xcm-transactor/primitives/src/lib.rs new file mode 100644 index 00000000..8ee058de --- /dev/null +++ b/frame/pallet-xcm-transactor/primitives/src/lib.rs @@ -0,0 +1,148 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::{RuntimeDebug, H160}; +use xcm::{latest::Weight, prelude::*}; + +pub const XCM_EXTENSION_ID: u16 = 04; + +#[repr(u16)] +#[derive(TryFromPrimitive, IntoPrimitive)] +pub enum Command { + /// Returns the weight for given XCM and saves it (in CE, per-call scratch buffer) for + /// execution + PrepareExecute = 0, + /// Execute the XCM that was prepared earlier + Execute = 1, + /// Returns the fee required to send XCM and saves it for sending + ValidateSend = 2, + /// Send the validated XCM + Send = 3, + /// Register the new query + NewQuery = 4, + /// Take the response for query if available + TakeResponse = 5, + /// Get the pallet account id which will call the contract callback + PalletAccountId = 6, +} + +/// Type of XCM Response Query +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +// #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum QueryType { + // No callback, store the response for manual polling + NoCallback, + // Call Wasm contract's method on recieving response + // It expects the contract method to have following signature + // - (query_id: QueryId, responder: Multilocation, response: Response) + WASMContractCallback { + contract_id: AccountId, + selector: [u8; 4], + }, + // Call Evm contract's method on recieving response + // It expects the contract method to have following signature + // - (query_id: QueryId, responder: Multilocation, response: Response) + EVMContractCallback { + contract_id: H160, + selector: [u8; 4], + }, +} + +/// Query config +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] +// #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct QueryConfig { + // query type + pub query_type: QueryType, + // blocknumber after which query will be expire + pub timeout: BlockNumber, +} + +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub struct ValidateSendInput { + pub dest: VersionedMultiLocation, + pub xcm: VersionedXcm<()>, +} + +pub struct PreparedExecution { + pub xcm: Xcm, + pub weight: Weight, +} + +pub struct ValidatedSend { + pub dest: MultiLocation, + pub xcm: Xcm<()>, +} + +#[macro_export] +macro_rules! create_error_enum { + ($vis:vis $type_name:ident) => { + #[repr(u32)] + #[derive( + ::core::cmp::PartialEq, + ::core::cmp::Eq, + ::core::marker::Copy, + ::core::clone::Clone, + // crate name mismatch, 'parity-scale-codec' is crate name but in ink! contract + // it is usually renamed to `scale` + Encode, + Decode, + ::core::fmt::Debug, + ::num_enum::IntoPrimitive, + ::num_enum::FromPrimitive, + )] + #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))] + $vis enum $type_name { + /// Success + Success = 0, + /// CE command not supported + InvalidCommand = 1, + /// The version of the Versioned value used is not able to be interpreted. + BadVersion = 2, + /// Origin not allow for registering queries + InvalidOrigin = 3, + /// Does not support the given query type + NotSupported = 4, + /// XCM execute preparation missing + PreparationMissing = 5, + /// Some of the XCM instructions failed to execute + ExecutionFailed = 6, + /// Failed to validate the XCM for sending + SendValidateFailed = 7, + /// Failed to send the XCM to destination + SendFailed = 8, + /// No response recieved for given query + NoResponse = 9, + /// Failed to weigh the XCM message + CannotWeigh = 10, + /// Querier mismatch + InvalidQuerier = 11, + /// Unknown runtime error + #[num_enum(default)] + RuntimeError = 99, + } + }; +} + +create_error_enum!(pub XcmCeError); diff --git a/frame/pallet-xcm-transactor/src/benchmarking.rs b/frame/pallet-xcm-transactor/src/benchmarking.rs new file mode 100644 index 00000000..df21dd07 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/benchmarking.rs @@ -0,0 +1,267 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_support::traits::Contains; +use sp_std::prelude::*; +use xcm::latest::prelude::*; + +fn mock_xcm_response( + response_info: QueryResponseInfo, + querier: impl Into, + response: Response, + weight_limit: Weight, +) { + let QueryResponseInfo { + destination, + query_id, + max_weight, + } = response_info; + let querier = Some(querier.into()); + let response_xcm = Xcm(vec![QueryResponse { + querier, + query_id, + max_weight, + response, + }]); + let hash = response_xcm.using_encoded(sp_io::hashing::blake2_256); + T::XcmExecutor::execute_xcm_in_credit( + destination, + response_xcm, + hash, + weight_limit, + weight_limit, + ); +} + +/// Assert that the last event equals the provided one. +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +#[benchmarks(where ::AccountId: From<[u8; 32]>)] +mod benchmarks_module { + + use sp_runtime::traits::Bounded; + + use super::*; + + #[benchmark] + fn account_id() { + #[block] + { + let _ = Pallet::::account_id(); + } + } + + #[benchmark] + fn prepare_execute() -> Result<(), BenchmarkError> { + let execute_origin = + T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let msg = Xcm(vec![ClearOrigin]); + let versioned_msg = VersionedXcm::from(msg); + + #[block] + { + let _ = Pallet::::prepare_execute(execute_origin, Box::new(versioned_msg)).unwrap(); + } + + Ok(()) + } + + #[benchmark] + fn execute() -> Result<(), BenchmarkError> { + let execute_origin = + T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + let msg = Xcm(vec![ClearOrigin]); + if !T::XcmExecuteFilter::contains(&(origin_location, msg.clone())) { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight( + Weight::MAX, + ))); + } + let versioned_msg = VersionedXcm::from(msg); + + #[block] + { + let _ = Pallet::::execute(execute_origin, Box::new(versioned_msg), Weight::zero()) + .unwrap(); + } + + Ok(()) + } + + #[benchmark] + fn validate_send() -> Result<(), BenchmarkError> { + let send_origin = + T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight( + Weight::MAX, + ))); + } + let msg = Xcm(vec![ClearOrigin]); + let versioned_dest: VersionedMultiLocation = T::ReachableDest::get() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight( + Weight::MAX, + )))? + .into(); + let versioned_msg = VersionedXcm::from(msg); + + #[block] + { + let _ = Pallet::::validate_send( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_msg), + ) + // not a good idea to unwrap here but it's the only way to + // check if it worked + .unwrap(); + } + + Ok(()) + } + + #[benchmark] + fn take_response() -> Result<(), BenchmarkError> { + let query_origin = T::RegisterQueryOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + if T::RegisterQueryOrigin::try_origin(query_origin.clone()).is_err() { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight( + Weight::MAX, + ))); + } + + let responder = (Parent, Parachain(1000)); + let weight_limit = Weight::from_parts(100_000_000_000, 1024 * 1024); + //register query + let query_id = Pallet::::new_query( + query_origin.clone(), + QueryConfig { + query_type: QueryType::NoCallback, + timeout: Bounded::max_value(), + }, + Box::new(responder.into()), + ) + .map_err(|_| BenchmarkError::Stop("Failed to register new query"))?; + // mock response + mock_xcm_response::( + QueryResponseInfo { + destination: responder.into(), + query_id, + max_weight: Weight::zero(), + }, + Here, + Response::Null, + weight_limit, + ); + + #[block] + { + let _ = Pallet::::take_response(query_origin.clone(), query_id); + } + + // make sure response is taken + assert_eq!(pallet_xcm::Pallet::::query(query_id), None); + assert_last_event::(Event::::ResponseTaken(query_id).into()); + Ok(()) + } + + #[benchmark] + fn new_query() -> Result<(), BenchmarkError> { + let query_origin = T::RegisterQueryOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + if T::RegisterQueryOrigin::try_origin(query_origin.clone()).is_err() { + return Err(BenchmarkError::Override(BenchmarkResult::from_weight( + Weight::MAX, + ))); + } + + let query_type = QueryType::WASMContractCallback { + contract_id: [0u8; 32].into(), + selector: [0u8; 4], + }; + let dest = (Parent, Parachain(1000)).into(); + + #[block] + { + let _ = Pallet::::new_query( + query_origin, + QueryConfig { + query_type: query_type.clone(), + timeout: Bounded::max_value(), + }, + Box::new(dest), + ); + } + + assert_last_event::( + Event::::QueryPrepared { + query_type, + // we are sure this will be the first query, so it fine to hardcode here + query_id: 0, + } + .into(), + ); + Ok(()) + } + + #[benchmark] + fn on_callback_recieved() { + let origin: ::RuntimeOrigin = ::RuntimeOrigin::from( + pallet_xcm::Origin::Response((Parent, Parachain(1000)).into()), + ) + .into(); + let query_id = 123; + let query_type = QueryType::NoCallback; + CallbackQueries::::insert( + query_id, + QueryInfo { + query_type: query_type.clone(), + // no use of querier, so any arbitary value will be fine + querier: Here, + }, + ); + + #[block] + { + let _ = Pallet::::on_callback_recieved(origin.into(), query_id, Response::Null); + } + + assert_eq!(CallbackQueries::::get(query_id), None); + assert_last_event::( + Event::::CallbackSuccess { + query_type, + query_id, + weight: Weight::zero(), + } + .into(), + ); + } + + impl_benchmark_test_suite! { + Pallet, + crate::mock::new_test_ext_with_balances(Vec::new()), + crate::mock::Test + } +} diff --git a/frame/pallet-xcm-transactor/src/chain_extension/mod.rs b/frame/pallet-xcm-transactor/src/chain_extension/mod.rs new file mode 100644 index 00000000..37982d49 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/chain_extension/mod.rs @@ -0,0 +1,279 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod weights; +use core::marker::PhantomData; + +use weights::CEWeightInfo; + +use crate::{Config, Error as PalletError, Pallet, QueryConfig}; +use frame_support::DefaultNoBound; +use frame_system::RawOrigin; +use pallet_contracts::chain_extension::{ + ChainExtension, Environment, Ext, InitState, Result as DispatchResult, RetVal, SysConfig, +}; +use pallet_xcm::{Pallet as XcmPallet, WeightInfo as PalletXcmWeightInfo}; +use parity_scale_codec::Encode; +use sp_std::prelude::*; +use xcm::prelude::*; +pub use xcm_ce_primitives::{ + Command::{self, *}, + PreparedExecution, ValidateSendInput, ValidatedSend, + XcmCeError::{self, *}, + XCM_EXTENSION_ID, +}; + +type RuntimeCallOf = ::RuntimeCall; + +macro_rules! unwrap { + ($val:expr) => { + match $val { + Ok(inner) => inner, + Err(e) => { + let err: XcmCeError = e.into(); + return Ok(RetVal::Converging(err.into())); + } + } + }; + ($val:expr, $err:expr) => { + match $val { + Ok(inner) => inner, + Err(_) => return Ok(RetVal::Converging($err.into())), + } + }; +} + +impl From> for XcmCeError { + fn from(value: PalletError) -> Self { + match value { + PalletError::BadVersion => BadVersion, + PalletError::InvalidOrigin => InvalidOrigin, + PalletError::NotSupported => NotSupported, + PalletError::SendValidateFailed => SendValidateFailed, + PalletError::CannotWeigh => CannotWeigh, + PalletError::InvalidQuerier => InvalidQuerier, + _ => RuntimeError, + } + } +} + +#[derive(DefaultNoBound)] +pub struct XCMExtension { + prepared_execute: Option>>, + validated_send: Option, + _w: PhantomData, +} + +impl ChainExtension for XCMExtension +where + ::AccountId: AsRef<[u8; 32]>, +{ + fn call(&mut self, env: Environment) -> DispatchResult + where + E: Ext, + { + match unwrap!(env.func_id().try_into(), InvalidCommand) { + PrepareExecute => self.prepare_execute(env), + Execute => self.execute(env), + ValidateSend => self.validate_send(env), + Send => self.send(env), + NewQuery => self.new_query(env), + TakeResponse => self.take_response(env), + PalletAccountId => self.pallet_account_id(env), + } + } +} + +impl XCMExtension { + /// Returns the weight for given XCM and saves it (in CE, per-call scratch buffer) for + /// execution + fn prepare_execute>( + &mut self, + env: Environment, + ) -> DispatchResult { + let mut env = env.buf_in_buf_out(); + // input parsing + let len = env.in_len(); + let input: VersionedXcm> = env.read_as_unbounded(len)?; + + // charge weight + env.charge_weight(W::prepare_execute(len))?; + + let origin = RawOrigin::Signed(env.ext().address().clone()); + let (xcm, weight) = unwrap!(Pallet::::prepare_execute(origin.into(), Box::new(input))); + + // save the prepared xcm + self.prepared_execute = Some(PreparedExecution { xcm, weight }); + // write the output to buffer + weight.using_encoded(|w| env.write(w, true, None))?; + + Ok(RetVal::Converging(XcmCeError::Success.into())) + } + + /// Execute the XCM that was prepared earlier + fn execute>( + &mut self, + mut env: Environment, + ) -> DispatchResult { + let PreparedExecution { xcm, weight } = unwrap!( + self.prepared_execute.as_ref().take().ok_or(()), + PreparationMissing + ); + // charge weight + let charged = env.charge_weight(W::execute().saturating_add(*weight))?; + // TODO: find better way to get origin + // https://github.com/paritytech/substrate/pull/13708 + let origin = RawOrigin::Signed(env.ext().address().clone()); + let outcome = unwrap!( + Pallet::::execute( + origin.into(), + Box::new(VersionedXcm::V3(xcm.clone())), + *weight, + ), + // TODO: mapp pallet error 1-1 with CE errors + InvalidOrigin + ); + // adjust with actual weights used + env.adjust_weight(charged, outcome.weight_used().saturating_add(W::execute())); + // revert for anything but a complete execution + match outcome { + Outcome::Complete(_) => Ok(RetVal::Converging(Success.into())), + _ => Ok(RetVal::Converging(ExecutionFailed.into())), + } + } + + /// Returns the fee required to send XCM and saves + /// it for sending + fn validate_send>( + &mut self, + env: Environment, + ) -> DispatchResult { + let mut env = env.buf_in_buf_out(); + let len = env.in_len(); + let ValidateSendInput { dest, xcm } = env.read_as_unbounded(len)?; + // charge weight + env.charge_weight(W::validate_send(len))?; + + let origin = RawOrigin::Signed(env.ext().address().clone()); + // validate and get fees required to send + let (xcm, dest, fees) = unwrap!(Pallet::::validate_send( + origin.into(), + Box::new(dest.clone()), + Box::new(xcm.clone()) + )); + // save the validated input + self.validated_send = Some(ValidatedSend { dest, xcm }); + // write the fees to output + VersionedMultiAssets::from(fees).using_encoded(|a| env.write(a, true, None))?; + Ok(RetVal::Converging(XcmCeError::Success.into())) + } + + /// Send the validated XCM + fn send>( + &mut self, + mut env: Environment, + ) -> DispatchResult { + let input = unwrap!( + self.validated_send.as_ref().take().ok_or(()), + PreparationMissing + ); + // charge weight + let base_weight = ::WeightInfo::send(); + env.charge_weight(base_weight.saturating_add(W::send()))?; + + // TODO: find better way to get origin + // https://github.com/paritytech/substrate/pull/13708 + let origin = RawOrigin::Signed(env.ext().address().clone()); + // send the xcm + unwrap!( + XcmPallet::::send( + origin.into(), + Box::new(input.dest.into()), + Box::new(xcm::VersionedXcm::V3(input.xcm.clone())), + ), + SendFailed + ); + + Ok(RetVal::Converging(XcmCeError::Success.into())) + } + + /// Register the new query + fn new_query>(&self, env: Environment) -> DispatchResult + where + ::AccountId: AsRef<[u8; 32]>, + { + let mut env = env.buf_in_buf_out(); + let len = env.in_len(); + let (query_config, dest): ( + QueryConfig, + VersionedMultiLocation, + ) = env.read_as_unbounded(len)?; + // charge weight + // NOTE: we only charge the weight associated with query registration and processing of + // calllback only. This does not include the CALLBACK weights + env.charge_weight(W::new_query())?; + + let origin = RawOrigin::Signed(env.ext().address().clone()); + // register the query + let query_id: u64 = Pallet::::new_query(origin.into(), query_config, Box::new(dest))?; + // write the query_id to buffer + query_id.using_encoded(|q| env.write(q, true, None))?; + Ok(RetVal::Converging(Success.into())) + } + + /// Take the response for query if available + /// TODO: figure out weights + fn take_response>( + &self, + env: Environment, + ) -> DispatchResult { + let mut env = env.buf_in_buf_out(); + let query_id: u64 = env.read_as()?; + // charge weight + env.charge_weight(W::take_response())?; + // TODO: find better way to get origin + // https://github.com/paritytech/substrate/pull/13708 + let origin = RawOrigin::Signed(env.ext().address().clone()); + let response = unwrap!( + unwrap!(Pallet::::take_response(origin.into(), query_id)) + .map(|r| r.0) + .ok_or(()), + NoResponse + ); + + VersionedResponse::from(response).using_encoded(|r| env.write(r, true, None))?; + Ok(RetVal::Converging(XcmCeError::Success.into())) + } + + /// Get the pallet account id which will call the contract callback + /// TODO: figure out weights + fn pallet_account_id>( + &self, + env: Environment, + ) -> DispatchResult { + let mut env = env.buf_in_buf_out(); + // charge weight + env.charge_weight(W::account_id())?; + + Pallet::::account_id().using_encoded(|r| env.write(r, true, None))?; + + Ok(RetVal::Converging(XcmCeError::Success.into())) + } +} diff --git a/frame/pallet-xcm-transactor/src/chain_extension/weights.rs b/frame/pallet-xcm-transactor/src/chain_extension/weights.rs new file mode 100644 index 00000000..f5cf1db0 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/chain_extension/weights.rs @@ -0,0 +1,73 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use crate::{weights::{WeightInfo, SubstrateWeight}, Config}; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_transactor chain extension. +pub trait CEWeightInfo { + fn account_id() -> Weight; + fn prepare_execute(len: u32) -> Weight; + fn execute() -> Weight; + fn validate_send(len: u32) -> Weight; + fn send() -> Weight; + fn take_response() -> Weight; + fn new_query() -> Weight; + fn read_as_unbounded(n: u32) -> Weight; +} + +/// Weights for pallet_xcm_transactor chain extension. +pub struct ChainExtensionWeight(PhantomData); +impl CEWeightInfo for ChainExtensionWeight { + fn account_id() -> Weight { + ::WeightInfo::account_id() + } + + fn prepare_execute(len: u32) -> Weight { + ::WeightInfo::prepare_execute().saturating_add(Self::read_as_unbounded(len)) + } + + fn execute() -> Weight { + ::WeightInfo::execute() + } + + fn validate_send(len: u32) -> Weight { + ::WeightInfo::validate_send().saturating_add(Self::read_as_unbounded(len)) + } + + fn send() -> Weight { + Weight::from_ref_time(100_000) + } + + fn take_response() -> Weight { + ::WeightInfo::take_response() + } + + fn new_query() -> Weight { + ::WeightInfo::new_query().saturating_add(::WeightInfo::on_callback_recieved()) + } + + fn read_as_unbounded(n: u32) -> Weight { + Weight::from_ref_time(1_000).saturating_mul(n.into()) + } +} diff --git a/frame/pallet-xcm-transactor/src/lib.rs b/frame/pallet-xcm-transactor/src/lib.rs new file mode 100644 index 00000000..d8296c11 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/lib.rs @@ -0,0 +1,509 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +//! Pallet to handle XCM Callbacks. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! - [`Event`] +//! +//! ## Overview +//! +//! The pallet provides functionality for xcm query management and handling callbacks: +//! +//! - Registering a new query +//! - Taking response of query (if available) +//! - Handling the pallet_xcm's OnResponse notify +//! +//! ### Terminology +//! +//! - **Callback:** When recieving the XCM response from pallet_xcm notify routing that +//! response to desired destination (like wasm contract) +//! - **Pallet XCM Notify:** The pallet_xcm OnResponse handler can call (notify) custom +//! disptach on recieving the XCM response given that query is registered before +//! hand. +//! - **Manual Polling:** Instead of callback, the response is saved for the user to manually +//! poll it. +//! +//! To use it in your runtime, you need to implement the pallet's [`Config`]. +//! +//! ### Implementation +//! +//! The pallet provides implementations for the following traits. +//! - [`OnCallback`](pallet_xcm_transactor::OnCallback): Functions for dealing when a +//! callback is recieved. +//! +//! ### Goals +//! The callback system is designed to make following possible: +//! +//! - Registeration of new query which can either be manual polling or callbacks +//! - Allow query owners to take the response in case of manual polling +//! - Handle the incoming pallet_xcm's notify and route it with help of `CallbackHandler` +//! +//! ## Interface +//! +//! ### Permissioned Functions +//! - `on_callback_recieved`: Accepts the XCM Response and invoke the `CallbackHandler`, can only +//! be called in a response to XCM. +//! +//! ### Public Functions +//! - `new_query`: Registers a new query and returns the query id +//! - `account_id`: Get the account id associated with this pallet that will be the origin +//! of the callback +//! - `take_response`: Take the response if available + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod chain_extension; +pub mod traits; +pub mod weights; +pub use chain_extension::weights::*; +pub use traits::*; +pub use weights::*; + +use frame_support::{pallet_prelude::*, PalletId}; +use frame_system::{pallet_prelude::*, Config as SysConfig}; +pub use pallet::*; +use pallet_contracts::Pallet as PalletContracts; +use pallet_xcm::Pallet as PalletXcm; +use sp_core::H160; +use sp_runtime::traits::{AccountIdConversion, Zero}; +use sp_std::prelude::*; +use xcm::prelude::*; +use xcm_executor::traits::WeightBounds; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::Config as SysConfig; + use pallet_xcm::ensure_response; + pub use xcm_ce_primitives::{QueryConfig, QueryType}; + + /// Response info + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct ResponseInfo { + pub query_id: QueryId, + pub query_type: QueryType, + pub response: Response, + } + + /// Query infor + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct QueryInfo { + pub query_type: QueryType, + pub querier: Junctions, + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm::Config + pallet_contracts::Config { + /// The overarching event type. + type RuntimeEvent: IsType<::RuntimeEvent> + From>; + + /// The overarching call type. + type RuntimeCall: Parameter + + From> + + IsType<::RuntimeCall>; + + /// The overaching origin type + type RuntimeOrigin: Into::RuntimeOrigin>> + + IsType<::RuntimeOrigin> + + From<::RuntimeOrigin>; + + /// Query Handler for creating quries and handling response + type CallbackHandler: OnCallback< + AccountId = Self::AccountId, + BlockNumber = Self::BlockNumber, + >; + + /// Required origin for registering new queries. If successful, it resolves to `MultiLocation` + /// which exists as an interior location within this chain's XCM context. + type RegisterQueryOrigin: EnsureOrigin< + ::RuntimeOrigin, + Success = MultiLocation, + >; + + /// Weights for pallet + type WeightInfo: WeightInfo; + + /// Max weight for callback + #[pallet::constant] + type MaxCallbackWeight: Get; + } + + /// Mapping of ongoing queries and thier type + #[pallet::storage] + #[pallet::getter(fn callback_query)] + pub(super) type CallbackQueries = + StorageMap<_, Blake2_128Concat, QueryId, QueryInfo, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// successfully handled callback + CallbackSuccess { + query_type: QueryType, + query_id: QueryId, + weight: Weight, + }, + CallbackFailed { + query_type: QueryType, + query_id: QueryId, + }, + /// new query registered + QueryPrepared { + query_type: QueryType, + query_id: QueryId, + }, + /// query response taken + ResponseTaken(QueryId), + } + + #[derive(PartialEq)] + #[pallet::error] + pub enum Error { + /// The version of the Versioned value used is not able to be interpreted. + BadVersion, + /// Origin not allow for registering queries + InvalidOrigin, + /// Query not found in storage + UnexpectedQuery, + /// Does not support the given query type + NotSupported, + /// Querier mismatch + InvalidQuerier, + /// Failed to weigh XCM message + CannotWeigh, + /// Failed to validate xcm for sending + SendValidateFailed, + /// Callback out of gas + /// TODO: use it + OutOfGas, + /// WASM Contract reverted + WASMContractReverted, + /// EVM Contract reverted + EVMContractReverted, + /// callback failed due to unkown reasons + /// TODO: split this error into known errors + CallbackFailed, + } + + #[pallet::call] + impl Pallet { + /// Dispatch for recieving callback from pallet_xcm's notify + /// and handle their routing + #[pallet::call_index(0)] + #[pallet::weight(T::MaxCallbackWeight::get())] + pub fn on_callback_recieved( + origin: OriginFor, + query_id: QueryId, + response: Response, + ) -> DispatchResult { + // ensure the origin is a response + let responder = ensure_response(::RuntimeOrigin::from(origin))?; + // fetch the query + let QueryInfo { query_type, .. } = + CallbackQueries::::get(query_id).ok_or(Error::::UnexpectedQuery)?; + + // handle the response routing + // TODO: in case of error, maybe save the response for manual + // polling as fallback. This will require taking into weight of storing + // response in the weights of `prepare_new_query` dispatch + match T::CallbackHandler::on_callback( + responder, + ResponseInfo { + query_id, + query_type: query_type.clone(), + response, + }, + ) { + Ok(weight) => { + // deposit success event + Self::deposit_event(Event::::CallbackSuccess { + query_type, + query_id, + weight, + }); + // remove the query + CallbackQueries::::remove(query_id); + Ok(()) + } + Err(e) => { + Self::deposit_event(Event::::CallbackFailed { + query_type, + query_id, + }); + Err(e.into()) + } + } + } + } +} + +impl OnCallback for Pallet { + type AccountId = T::AccountId; + type BlockNumber = T::BlockNumber; + type Error = Error; + + fn can_handle(query_type: &QueryType) -> bool { + match query_type { + QueryType::NoCallback => true, + QueryType::WASMContractCallback { .. } => true, + // TODO: add support for evm contracts + QueryType::EVMContractCallback { .. } => false, + } + } + + fn on_callback( + responder: impl Into, + response_info: ResponseInfo, + ) -> Result { + let ResponseInfo { + query_id, + query_type, + response, + } = response_info; + + match query_type { + QueryType::NoCallback => { + // TODO: Nothing to do, maybe error? + Ok(Weight::zero()) + } + QueryType::WASMContractCallback { + contract_id, + selector, + } => Self::call_wasm_contract_method( + contract_id, + selector, + query_id, + responder.into(), + response, + ), + QueryType::EVMContractCallback { + contract_id, + selector, + } => Self::call_evm_contract_method( + contract_id, + selector, + query_id, + responder.into(), + response, + ), + } + } +} + +/// Public methods +impl Pallet { + /// The account ID of the pallet. + pub fn account_id() -> T::AccountId { + const ID: PalletId = PalletId(*b"py/xcmnt"); + AccountIdConversion::::into_account_truncating(&ID) + } + + /// Weigh the XCM to prepare for execution + pub fn prepare_execute( + origin: OriginFor, + xcm: Box::RuntimeCall>>, + ) -> Result<(Xcm<::RuntimeCall>, Weight), Error> { + T::ExecuteXcmOrigin::ensure_origin(origin).map_err(|_| Error::InvalidOrigin)?; + + let mut xcm = (*xcm).try_into().map_err(|_| Error::BadVersion)?; + let weight = T::Weigher::weight(&mut xcm).map_err(|_| Error::CannotWeigh)?; + Ok((xcm, weight)) + } + + pub fn execute( + origin: OriginFor, + xcm: Box::RuntimeCall>>, + weight_limit: Weight, + ) -> Result> { + let origin_location = + T::ExecuteXcmOrigin::ensure_origin(origin).map_err(|_| Error::InvalidOrigin)?; + let xcm: Xcm<::RuntimeCall> = + (*xcm).try_into().map_err(|_| Error::BadVersion)?; + + // execute XCM + // NOTE: not using pallet_xcm::execute here because it does not return XcmError + // which is needed to ensure xcm execution success + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(T::XcmExecutor::execute_xcm_in_credit( + origin_location, + xcm.clone(), + hash, + weight_limit, + weight_limit, + )) + } + + pub fn validate_send( + origin: OriginFor, + dest: Box, + xcm: Box>, + ) -> Result<(Xcm<()>, MultiLocation, VersionedMultiAssets), Error> { + T::SendXcmOrigin::ensure_origin(origin).map_err(|_| Error::InvalidOrigin)?; + let xcm: Xcm<()> = (*xcm).try_into().map_err(|_| Error::BadVersion)?; + let dest: MultiLocation = (*dest).try_into().map_err(|_| Error::BadVersion)?; + + let (_, fees) = validate_send::(dest.clone(), xcm.clone()) + .map_err(|_| Error::SendValidateFailed)?; + Ok((xcm, dest, VersionedMultiAssets::V3(fees))) + } + + /// Take the response if available and querier matches + pub fn take_response( + origin: OriginFor, + query_id: QueryId, + ) -> Result, Error> { + // ensure origin is allowed to make queries + let origin_location: Junctions = T::RegisterQueryOrigin::ensure_origin(origin) + .map_err(|_| Error::InvalidOrigin)? + .try_into() + .map_err(|_| Error::InvalidOrigin)?; + + let response = Self::do_take_response(origin_location, query_id)?; + Self::deposit_event(Event::ResponseTaken(query_id)); + Ok(response) + } + + /// Register new query originating from querier to dest + pub fn new_query( + origin: OriginFor, + config: QueryConfig, + dest: Box, + ) -> Result> { + let origin_location = + T::RegisterQueryOrigin::ensure_origin(origin).map_err(|_| Error::InvalidOrigin)?; + let interior: Junctions = origin_location + .try_into() + .map_err(|_| Error::::InvalidOrigin)?; + let query_type = config.query_type.clone(); + let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; + + // register query + let query_id = Self::do_new_query(config, interior, dest)?; + Self::deposit_event(Event::::QueryPrepared { + query_type, + query_id, + }); + Ok(query_id) + } +} + +/// Internal methods +impl Pallet { + /// Register new query originating from querier to dest + fn do_new_query( + QueryConfig { + query_type, + timeout, + }: QueryConfig, + querier: impl Into, + dest: impl Into, + ) -> Result> { + let querier = querier.into(); + + // check if with callback handler + if !(T::CallbackHandler::can_handle(&query_type)) { + return Err(Error::NotSupported); + } + let id = match query_type.clone() { + QueryType::NoCallback => PalletXcm::::new_query(dest, timeout, querier), + QueryType::WASMContractCallback { .. } | QueryType::EVMContractCallback { .. } => { + let call: ::RuntimeCall = Call::on_callback_recieved { + query_id: 0, + response: Response::Null, + } + .into(); + PalletXcm::::new_notify_query(dest, call, timeout, querier.clone()) + } + }; + + CallbackQueries::::insert( + id, + QueryInfo { + query_type, + querier, + }, + ); + Ok(id) + } + + fn do_take_response( + querier: impl Into, + query_id: QueryId, + ) -> Result, Error> { + let query_info = CallbackQueries::::get(query_id).ok_or(Error::::UnexpectedQuery)?; + + if querier.into() == query_info.querier { + let response = pallet_xcm::Pallet::::take_response(query_id); + Self::deposit_event(Event::ResponseTaken(query_id)); + Ok(response) + } else { + Err(Error::::InvalidQuerier) + } + } + + fn call_wasm_contract_method( + contract_id: T::AccountId, + selector: [u8; 4], + query_id: QueryId, + responder: MultiLocation, + response: Response, + ) -> Result> { + // TODO: Use responder to derieve a origin account id + let outcome = PalletContracts::::bare_call( + Self::account_id(), + contract_id, + Zero::zero(), + T::MaxCallbackWeight::get(), + None, + [selector.to_vec(), (query_id, responder, response).encode()].concat(), + // TODO: should not be true + true, + pallet_contracts::Determinism::Deterministic, + ); + + let retval = outcome.result.map_err(|_| Error::CallbackFailed)?; + if retval.did_revert() { + Err(Error::WASMContractReverted) + } else { + Ok(outcome.gas_consumed) + } + } + + fn call_evm_contract_method( + _contract_id: H160, + _selector: [u8; 4], + _query_id: QueryId, + _responder: MultiLocation, + _response: Response, + ) -> Result> { + Ok(Weight::zero()) + } +} diff --git a/frame/pallet-xcm-transactor/src/mock.rs b/frame/pallet-xcm-transactor/src/mock.rs new file mode 100644 index 00000000..4551b334 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/mock.rs @@ -0,0 +1,330 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use frame_support::{ + construct_runtime, parameter_types, + traits::{Currency, Everything, Nothing}, + weights::Weight, +}; +use pallet_contracts::{chain_extension::RegisteredChainExtension, DefaultAddressGenerator, Frame}; +use pallet_xcm::TestWeightInfo; +use parity_scale_codec::Encode; +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::origin; +use sp_core::{ConstU32, ConstU64, H256}; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; +pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData}; +use xcm::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, + ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, + FixedRateOfFungible, FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, +}; +use xcm_ce_primitives::XCM_EXTENSION_ID; +use xcm_executor::XcmExecutor; + +use crate::{self as pallet_xcm_transactor, chain_extension::XCMExtension}; + +pub type AccountId = AccountId32; +pub type Balance = u128; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Randomness: pallet_insecure_randomness_collective_flip::{Pallet, Storage}, + ParasOrigin: origin::{Pallet, Origin}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config}, + Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, + XcmTransact: pallet_xcm_transactor::{Pallet, Call, Storage, Event}, + } +); + +impl pallet_insecure_randomness_collective_flip::Config for Test {} +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +thread_local! { + pub static SENT_XCM: RefCell)>> = RefCell::new(Vec::new()); +} +// pub(crate) fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> { +// SENT_XCM.with(|q| (*q.borrow()).clone()) +// } +// pub(crate) fn take_sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> { +// SENT_XCM.with(|q| { +// let mut r = Vec::new(); +// std::mem::swap(&mut r, &mut *q.borrow_mut()); +// r +// }) +// } +/// Sender that never returns error, always sends +pub struct TestSendXcm; +impl SendXcm for TestSendXcm { + type Ticket = (MultiLocation, Xcm<()>); + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult<(MultiLocation, Xcm<()>)> { + let pair = (dest.take().unwrap(), msg.take().unwrap()); + Ok((pair, MultiAssets::new())) + } + fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result { + let hash = fake_message_hash(&pair.1); + SENT_XCM.with(|q| q.borrow_mut().push(pair)); + Ok(hash) + } +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Test { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const RelayLocation: MultiLocation = Here.into_location(); + pub const AnyNetwork: Option = None; + pub UniversalLocation: InteriorMultiLocation = Here; + pub UnitWeightCost: u64 = 1_000; +} + +pub type SovereignAccountOf = ( + ChildParachainConvertsVia, + AccountId32Aliases, +); + +pub type LocalAssetTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = Weight::from_parts(1_000, 1_000); + pub CurrencyPerSecondPerByte: (AssetId, u128, u128) = (Concrete(RelayLocation::get()), 1, 1); + pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowKnownQueryResponses, + AllowSubscriptionsFrom, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = TestSendXcm; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = Case; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = (); + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); +} + +impl pallet_xcm::Config for Test { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = TestSendXcm; + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type TrustedLockers = (); + type SovereignAccountOf = AccountId32Aliases<(), AccountId32>; + type Currency = Balances; + type CurrencyMatcher = IsConcrete; + type MaxLockers = frame_support::traits::ConstU32<8>; + type WeightInfo = TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +parameter_types! { + pub const CallbackGasLimit: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000); + pub static UnstableInterface: bool = true; + pub Schedule: pallet_contracts::Schedule = Default::default(); + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 1; +} + +impl RegisteredChainExtension + for XCMExtension +{ + const ID: u16 = XCM_EXTENSION_ID; +} + +impl pallet_contracts::Config for Test { + type Time = Timestamp; + type Randomness = Randomness; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type CallFilter = Nothing; + type CallStack = [Frame; 5]; + type WeightPrice = (); + type WeightInfo = (); + type ChainExtension = XCMExtension>; + type DeletionQueueDepth = ConstU32<1024>; + type DeletionWeightLimit = DeletionWeightLimit; + type Schedule = Schedule; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = UnstableInterface; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; +} + +impl pallet_xcm_transactor::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type CallbackHandler = XcmTransact; + type RegisterQueryOrigin = EnsureXcmOrigin; + type MaxCallbackWeight = CallbackGasLimit; + type WeightInfo = pallet_xcm_transactor::SubstrateWeight; +} + +impl origin::Config for Test {} + +pub(crate) fn new_test_ext_with_balances( + balances: Vec<(AccountId, Balance)>, +) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + + >::assimilate_storage( + &pallet_xcm::GenesisConfig { + safe_xcm_version: Some(2), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn fake_message_hash(message: &Xcm) -> XcmHash { + message.using_encoded(sp_io::hashing::blake2_256) +} diff --git a/frame/pallet-xcm-transactor/src/traits.rs b/frame/pallet-xcm-transactor/src/traits.rs new file mode 100644 index 00000000..aed00985 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/traits.rs @@ -0,0 +1,78 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; + +use frame_support::weights::Weight; +use sp_runtime::DispatchError; +use xcm::prelude::*; +use xcm_ce_primitives::QueryType; + +use crate::{Config, ResponseInfo}; + +/// Handle the incoming xcm notify callback from ResponseHandler (pallet_xcm) +pub trait OnCallback { + /// error type, that can be converted to dispatch error + type Error: Into; + /// account id type + type AccountId; + /// blocknumber type + type BlockNumber; + + // TODO: Query type itself should be generic like + // + // type QueryType: Member + Parameter + MaybeSerializeDeserialize + MaxEncodedLen + Convert + // type CallbackHandler: OnResponse + // + // #[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen)] + // enum MyQueryType {} + // + // impl Convert for MyQueryType {} + + /// Check whether query type is supported or not + fn can_handle(query_type: &QueryType) -> bool; + + /// handle the xcm response + fn on_callback( + responder: impl Into, + response_info: ResponseInfo, + ) -> Result; +} + +/// OnCallback implementation that does not supports any callback +/// Use this to disable callbacks +pub struct NoCallback(PhantomData); +impl OnCallback for NoCallback { + type Error = crate::Error; + type AccountId = T::AccountId; + type BlockNumber = T::BlockNumber; + + fn can_handle(_: &QueryType) -> bool { + false + } + + fn on_callback( + _: impl Into, + _: ResponseInfo, + ) -> Result { + Ok(Weight::zero()) + } +} diff --git a/frame/pallet-xcm-transactor/src/weights.rs b/frame/pallet-xcm-transactor/src/weights.rs new file mode 100644 index 00000000..204ad3b4 --- /dev/null +++ b/frame/pallet-xcm-transactor/src/weights.rs @@ -0,0 +1,192 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +//! Autogenerated weights for pallet_xcm_transactor +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-05-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `AV`, CPU: `AMD Ryzen 7 5800H with Radeon Graphics` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("shibuya-dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=shibuya-dev +// --pallet=pallet_xcm_transactor +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --steps=50 +// --repeat=20 +// --template=./scripts/templates/weight-template.hbs +// --output=weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_xcm_transactor. +pub trait WeightInfo { + fn account_id() -> Weight; + fn prepare_execute() -> Weight; + fn execute() -> Weight; + fn validate_send() -> Weight; + fn take_response() -> Weight; + fn new_query() -> Weight; + fn on_callback_recieved() -> Weight; +} + +/// Weights for pallet_xcm_transactor using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn account_id() -> Weight { + // Minimum execution time: 831 nanoseconds. + Weight::from_ref_time(921_000) + .saturating_add(Weight::from_proof_size(0)) + } + fn prepare_execute() -> Weight { + // Minimum execution time: 1_964 nanoseconds. + Weight::from_ref_time(2_454_000) + .saturating_add(Weight::from_proof_size(0)) + } + // Storage: Benchmark Override (r:0 w:0) + // Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn execute() -> Weight { + // Minimum execution time: 18_446_744_073_709_551 nanoseconds. + Weight::from_ref_time(18_446_744_073_709_551_000) + .saturating_add(Weight::from_proof_size(0)) + } + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) + fn validate_send() -> Weight { + // Minimum execution time: 16_070 nanoseconds. + Weight::from_ref_time(16_421_000) + .saturating_add(Weight::from_proof_size(5274)) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + // Storage: XcmTransact CallbackQueries (r:1 w:0) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + // Storage: PolkadotXcm Queries (r:1 w:1) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + fn take_response() -> Weight { + // Minimum execution time: 46_087 nanoseconds. + Weight::from_ref_time(47_109_000) + .saturating_add(Weight::from_proof_size(6755)) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + // Storage: PolkadotXcm QueryCounter (r:1 w:1) + // Proof Skipped: PolkadotXcm QueryCounter (max_values: Some(1), max_size: None, mode: Measured) + // Storage: XcmTransact CallbackQueries (r:0 w:1) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + // Storage: PolkadotXcm Queries (r:0 w:1) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + fn new_query() -> Weight { + // Minimum execution time: 29_375 nanoseconds. + Weight::from_ref_time(31_400_000) + .saturating_add(Weight::from_proof_size(2367)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + // Storage: XcmTransact CallbackQueries (r:1 w:1) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + fn on_callback_recieved() -> Weight { + // Minimum execution time: 28_414 nanoseconds. + Weight::from_ref_time(29_886_000) + .saturating_add(Weight::from_proof_size(3137)) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn account_id() -> Weight { + // Minimum execution time: 831 nanoseconds. + Weight::from_ref_time(921_000) + .saturating_add(Weight::from_proof_size(0)) + } + fn prepare_execute() -> Weight { + // Minimum execution time: 1_964 nanoseconds. + Weight::from_ref_time(2_454_000) + .saturating_add(Weight::from_proof_size(0)) + } + // Storage: Benchmark Override (r:0 w:0) + // Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn execute() -> Weight { + // Minimum execution time: 18_446_744_073_709_551 nanoseconds. + Weight::from_ref_time(18_446_744_073_709_551_000) + .saturating_add(Weight::from_proof_size(0)) + } + // Storage: PolkadotXcm SupportedVersion (r:1 w:0) + // Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + // Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) + // Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) + // Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) + // Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) + fn validate_send() -> Weight { + // Minimum execution time: 16_070 nanoseconds. + Weight::from_ref_time(16_421_000) + .saturating_add(Weight::from_proof_size(5274)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + // Storage: XcmTransact CallbackQueries (r:1 w:0) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + // Storage: PolkadotXcm Queries (r:1 w:1) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + fn take_response() -> Weight { + // Minimum execution time: 46_087 nanoseconds. + Weight::from_ref_time(47_109_000) + .saturating_add(Weight::from_proof_size(6755)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + // Storage: PolkadotXcm QueryCounter (r:1 w:1) + // Proof Skipped: PolkadotXcm QueryCounter (max_values: Some(1), max_size: None, mode: Measured) + // Storage: XcmTransact CallbackQueries (r:0 w:1) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + // Storage: PolkadotXcm Queries (r:0 w:1) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + fn new_query() -> Weight { + // Minimum execution time: 29_375 nanoseconds. + Weight::from_ref_time(31_400_000) + .saturating_add(Weight::from_proof_size(2367)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + // Storage: XcmTransact CallbackQueries (r:1 w:1) + // Proof: XcmTransact CallbackQueries (max_values: None, max_size: Some(662), added: 3137, mode: MaxEncodedLen) + fn on_callback_recieved() -> Weight { + // Minimum execution time: 28_414 nanoseconds. + Weight::from_ref_time(29_886_000) + .saturating_add(Weight::from_proof_size(3137)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/pallet-xcm-transactor/xcm-simulator/.gitignore b/frame/pallet-xcm-transactor/xcm-simulator/.gitignore new file mode 100644 index 00000000..116caa12 --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/.gitignore @@ -0,0 +1 @@ +fixtures diff --git a/frame/pallet-xcm-transactor/xcm-simulator/Cargo.toml b/frame/pallet-xcm-transactor/xcm-simulator/Cargo.toml new file mode 100644 index 00000000..71ca6123 --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/Cargo.toml @@ -0,0 +1,96 @@ +[package] +name = "xcm-simulator-tests" +version = "0.1.0" +description = "XCM Simulator tests" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dev-dependencies] +log = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +# Base functionality +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } +pallet-contracts = { workspace = true } +pallet-contracts-primitives = { workspace = true } +pallet-insecure-randomness-collective-flip = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +sp-tracing = { workspace = true } + +# Custom Astar inclusions +pallet-dapps-staking = { workspace = true } +pallet-xc-asset-config = { workspace = true } +xcm-primitives = { path = "../../../primitives/xcm", default-features = false } + +# polkadot deps +polkadot-primitives = { workspace = true } + +# XCM +cumulus-pallet-xcm = { workspace = true } +pallet-xcm = { workspace = true } +polkadot-core-primitives = { workspace = true } +polkadot-parachain = { workspace = true } +polkadot-runtime-parachains = { workspace = true } +xcm = { workspace = true } +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } +xcm-simulator = { workspace = true } + +pallet-xcm-transactor = { path = "../", default-features = false } + +[build-dependencies] +# Currently using patched version due to issue with `RUSTFLAGS` inside build script. +# cargo-build sets the `RUSTFLAGS` for adding linker flags which are not applied when +# invoking it inside a build script, thus contract compilation fails. +# Fix - use `CARGO_ENCODED_RUSTFLAGS` instead of `RUSTFLAGS` +# https://github.com/rust-lang/cargo/issues/10111 +# TODO: remove this once it is merged in upstream +contract-build = { git = "https://github.com/ashutoshvarma/cargo-contract", branch = "patch/fix-rustflags" } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-tracing/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-timestamp/std", + "pallet-contracts/std", + "pallet-contracts-primitives/std", + "pallet-insecure-randomness-collective-flip/std", + "pallet-xcm/std", + "cumulus-pallet-xcm/std", + "pallet-assets/std", + "xcm-primitives/std", + "polkadot-primitives/std", + "pallet-dapps-staking/std", + "pallet-xcm-transactor/std", +] +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-contracts/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", + "polkadot-parachain/runtime-benchmarks", + "pallet-xcm-transactor/runtime-benchmarks", +] diff --git a/frame/pallet-xcm-transactor/xcm-simulator/build.rs b/frame/pallet-xcm-transactor/xcm-simulator/build.rs new file mode 100644 index 00000000..020d58dd --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/build.rs @@ -0,0 +1,108 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +/// build.rs +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use contract_build::{ + BuildArtifacts, BuildMode, Features, ManifestPath, Network, OptimizationPasses, OutputType, + Target, UnstableFlags, Verbosity, +}; + +/// Execute the clousre with given directory as current dir +fn with_directory T>(f: F, dir: &Path) -> T { + let curr_dir = std::env::current_dir().unwrap(); + + std::env::set_current_dir(dir).unwrap(); + let res = f(); + std::env::set_current_dir(curr_dir).unwrap(); + + res +} + +/// Build the contracts and copy the artifacts to fixtures dir +fn build_contract(fixtures_dir: &Path, dir: &Path, name: &str) { + println!("[build.rs] Building Contract - {name}"); + + let build = with_directory( + || { + let manifest_path = ManifestPath::new("Cargo.toml").unwrap(); + + let args = contract_build::ExecuteArgs { + manifest_path, + verbosity: Verbosity::Verbose, + build_mode: BuildMode::Debug, + features: Features::default(), + network: Network::Online, + build_artifact: BuildArtifacts::All, + unstable_flags: UnstableFlags::default(), + optimization_passes: Some(OptimizationPasses::default()), + keep_debug_symbols: true, + lint: false, + output_type: OutputType::HumanReadable, + skip_wasm_validation: false, + target: Target::Wasm, + }; + + contract_build::execute(args).expect(&format!("Failed to build contract at - {dir:?}")) + }, + dir, + ); + + // copy wasm artifact + fs::copy( + build.dest_wasm.unwrap(), + fixtures_dir.join(format!("{name}.wasm")), + ) + .unwrap(); + + // copy metadata + fs::copy( + build.metadata_result.unwrap().dest_metadata, + fixtures_dir.join(format!("{name}.json")), + ) + .unwrap(); +} + +fn setup() -> (PathBuf, PathBuf) { + let fixture_env = std::env::var("CB_FIXTURES_DIR").unwrap_or("fixtures".to_string()); + let fixtures_dir = Path::new(&fixture_env); + + let contracts_env = + std::env::var("CB_CONTRACTS_DIR").unwrap_or("../contract-examples".to_string()); + let contracts_dir = Path::new(&contracts_env); + + // create fixtures dir if not exists + fs::create_dir_all(fixtures_dir).unwrap(); + + (fixtures_dir.to_path_buf(), contracts_dir.to_path_buf()) +} + +fn main() { + let (fixtures_dir, contracts_dir) = setup(); + build_contract( + &fixtures_dir, + &contracts_dir.join("basic-flip"), + "basic_flip", + ); + + println!("cargo:rerun-if-changed={}", contracts_dir.to_str().unwrap()); +} diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/README.md b/frame/pallet-xcm-transactor/xcm-simulator/src/README.md new file mode 100644 index 00000000..1e9fdc18 --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/README.md @@ -0,0 +1,22 @@ +# XCM Simulator Test Framework + +The `xcm-simulator` framework provides a nice & easy way to test complex XCM behavior in the form of a unit test. + +An entire system can be defined and/or mocked, which includes the relay chain and multiple parachains. +Sending of DMP, UMP and HRMP messages is supported. + +Tester controls when XCM message is dispatched and when it's received by the destination chain. + +# Structure + +A custom relay-chain runtime is defined in this crate. + +A custom parachain runtime is defined, loosely based on the `Shiden` runtime. +In current test setup, the same runtime is used to define two different parachains. +It's possible that in the future we decide to define a different runtime type (perhaps more resembling that of `Shibuya`). + +# Running Tests + +Running tests is same as with any other unit tests: + +`cargo test -p xcm-simulator-tests` \ No newline at end of file diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/lib.rs b/frame/pallet-xcm-transactor/xcm-simulator/src/lib.rs new file mode 100644 index 00000000..991f521c --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/lib.rs @@ -0,0 +1,285 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#[cfg(test)] +mod mocks; + +#[cfg(test)] +mod tests { + use crate::mocks::{parachain, *}; + + use frame_support::{assert_ok, weights::Weight}; + use pallet_contracts::Determinism; + use pallet_xcm_transactor::{ + chain_extension::{ValidateSendInput, XcmCeError as XcmCEError}, + QueryConfig, QueryType, + }; + use parity_scale_codec::{Decode, Encode}; + use sp_runtime::traits::Bounded; + use xcm::prelude::*; + use xcm_simulator::TestExt; + + type AccoundIdOf = ::AccountId; + type BlockNumberOf = ::BlockNumber; + + const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + + const SELECTOR_CONSTRUCTOR: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF]; + // const SELECTOR_EXECUTE: [u8; 4] = [0x11, 0x11, 0x11, 0x11]; + const SELECTOR_SEND: [u8; 4] = [0x22, 0x22, 0x22, 0x22]; + const SELECTOR_QUERY: [u8; 4] = [0x33, 0x33, 0x33, 0x33]; + const SELECTOR_HANDLE_RESPONSE: [u8; 4] = [0x55, 0x55, 0x55, 0x55]; + const SELECTOR_GET: [u8; 4] = [0x66, 0x66, 0x66, 0x66]; + + struct Fixture { + pub basic_flip_id: parachain::AccountId, + } + + /// Deploy the xcm flipper contract in ParaA and fund it's derive account + /// in ParaB + fn xcm_flipper_fixture() -> Fixture { + // deploy and initialize xcm flipper contract with `false` in ParaA + let mut basic_flip_id = [0u8; 32].into(); + ParaA::execute_with(|| { + (basic_flip_id, _) = deploy_contract::( + "basic_flip", + ALICE.into(), + 0, + GAS_LIMIT, + None, + // selector + true + SELECTOR_CONSTRUCTOR.to_vec(), + ); + + // check for flip status, should be false + let outcome = ParachainContracts::bare_call( + ALICE.into(), + basic_flip_id.clone(), + 0, + GAS_LIMIT, + None, + SELECTOR_GET.to_vec(), + true, + Determinism::Deterministic, + ); + let res = outcome.result.unwrap(); + // check for revert + assert!(!res.did_revert()); + // decode the return value + let flag = Result::::decode(&mut res.data.as_ref()).unwrap(); + assert_eq!(flag, Ok(false)); + }); + + // transfer funds to contract derieve account in ParaB + ParaB::execute_with(|| { + use parachain::System; + + let account = sibling_para_account_account_id(1, basic_flip_id.clone()); + assert_ok!(ParachainBalances::transfer( + parachain::RuntimeOrigin::signed(ALICE), + account, + INITIAL_BALANCE / 2, + )); + + System::reset_events(); + }); + + Fixture { basic_flip_id } + } + + // /// Execute XCM from contract via CE + // #[test] + // fn test_ce_execute() { + // MockNet::reset(); + + // let Fixture { + // basic_flip_id: contract_id, + // } = xcm_flipper_fixture(); + + // // + // // check the execute + // // + // ParaA::execute_with(|| { + // let transfer_amount = 100_000; + // // transfer some native to contract + // assert_ok!(ParachainBalances::transfer( + // parachain::RuntimeOrigin::signed(ALICE), + // contract_id.clone(), + // transfer_amount, + // )); + + // let xcm: Xcm<()> = Xcm(vec![ + // WithdrawAsset((Here, transfer_amount).into()), + // BuyExecution { + // fees: (Here, transfer_amount).into(), + // weight_limit: Unlimited, + // }, + // DepositAsset { + // assets: All.into(), + // beneficiary: AccountId32 { + // network: None, + // id: ALICE.into(), + // } + // .into(), + // }, + // ]); + + // // run execute in contract + // let alice_balance_before = ParachainBalances::balance(&ALICE.into()); + // let (res, _, _) = + // call_contract_method::, ()>>( + // ALICE.into(), + // contract_id.clone(), + // 0, + // Weight::from_parts(u64::MAX, 1024 * 1024 * 10), + // None, + // [SELECTOR_EXECUTE.to_vec(), VersionedXcm::V3(xcm).encode()].concat(), + // true, + // ); + + // assert_eq!(res, Ok(Ok(Weight::from_parts(30, 0)))); + // assert!( + // // TODO: since bare_call doesn't charge, use call + // ParachainBalances::balance(&ALICE.into()) == alice_balance_before + transfer_amount + // ); + // }); + // } + + // /// Send the XCM and handle response callback via CE + #[test] + fn test_ce_wasm_callback() { + MockNet::reset(); + + let Fixture { + basic_flip_id: contract_id, + } = xcm_flipper_fixture(); + + // + // Check send & callback query + // + ParaA::execute_with(|| { + use parachain::{Runtime, RuntimeCall}; + + let remark_call = RuntimeCall::System(frame_system::Call::remark_with_event { + remark: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + }); + + let config = QueryConfig::, BlockNumberOf> { + query_type: QueryType::WASMContractCallback { + contract_id: contract_id.clone(), + selector: SELECTOR_HANDLE_RESPONSE, + }, + timeout: Bounded::max_value(), + }; + let dest: VersionedMultiLocation = (Parent, Parachain(2)).into(); + + // register the callback query + let (res, _, _) = call_contract_method::< + parachain::Runtime, + Result, ()>, + >( + ALICE.into(), + contract_id.clone(), + 0, + GAS_LIMIT, + None, + [ + SELECTOR_QUERY.to_vec(), + (config.clone(), dest.clone()).encode(), + ] + .concat(), + true, + ); + assert_eq!(res, Ok(Ok(0))); + let query_id = res.unwrap().unwrap(); + + let xcm: Xcm<()> = Xcm(vec![ + WithdrawAsset((Here, INITIAL_BALANCE / 2).into()), + BuyExecution { + fees: (Here, INITIAL_BALANCE / 2).into(), + weight_limit: Unlimited, + }, + SetAppendix(Xcm(vec![ReportTransactStatus(QueryResponseInfo { + destination: (Parent, Parachain(1)).into(), + query_id, + max_weight: parachain::CallbackGasLimit::get(), + })])), + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most: Weight::from_parts( + 100_000_000_000_000, + 1024 * 1024 * 1024, + ), + call: remark_call.encode().into(), + }, + ]); + + // send xcm + let (_res, _, _) = call_contract_method::< + parachain::Runtime, + Result, ()>, + >( + ALICE.into(), + contract_id.clone(), + 0, + GAS_LIMIT, + None, + [ + SELECTOR_SEND.to_vec(), + ValidateSendInput { + dest, + xcm: VersionedXcm::V3(xcm), + } + .encode(), + ] + .concat(), + true, + ); + + // dbg!(res); + }); + + // check if remark was executed in ParaB + ParaB::execute_with(|| { + use parachain::{RuntimeEvent, System}; + // check remark events + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::System(frame_system::Event::Remarked { .. }) + ))); + + // clear the events + System::reset_events(); + }); + + // check for callback, if callback success then flip=true + ParaA::execute_with(|| { + // check for flip status + let (res, _, _) = call_contract_method::>( + ALICE.into(), + contract_id.clone(), + 0, + GAS_LIMIT, + None, + SELECTOR_GET.to_vec(), + true, + ); + assert_eq!(res, Ok(true)); + }); + } +} diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/mod.rs b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/mod.rs new file mode 100644 index 00000000..df91aa68 --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/mod.rs @@ -0,0 +1,317 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +pub(crate) mod msg_queue; +pub(crate) mod parachain; +pub(crate) mod relay_chain; + +use frame_support::traits::{Currency, IsType, OnInitialize}; +use frame_support::weights::Weight; +use pallet_contracts::Determinism; +use pallet_contracts_primitives::{Code, ReturnFlags}; +use parity_scale_codec::Decode; +use sp_runtime::traits::{Bounded, Hash, StaticLookup}; +use sp_runtime::DispatchResult; +use xcm::latest::prelude::*; +use xcm_executor::traits::Convert; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +type ContractBalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0xFAu8; 32]); +pub const INITIAL_BALANCE: u128 = 1_000_000_000_000_000_000_000_000; +pub const DAPP_STAKER_REWARD_PER_BLOCK: parachain::Balance = 1_000; +pub const DAPP_STAKER_DEV_PER_BLOCK: parachain::Balance = 250; + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_parachain! { + pub struct ParaB { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(2), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + XcmConfig = relay_chain::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + (2, ParaB), + ], + } +} + +// pub type RelayChainPalletXcm = pallet_xcm::Pallet; + +// pub type ParachainPalletXcm = pallet_xcm::Pallet; +// pub type ParachainAssets = pallet_assets::Pallet; +pub type ParachainBalances = pallet_balances::Pallet; +pub type ParachainContracts = pallet_contracts::Pallet; + +// pub fn parent_account_id() -> parachain::AccountId { +// let location = (Parent,); +// parachain::LocationToAccountId::convert(location.into()).unwrap() +// } + +/// Derive parachain sovereign account on relay chain, from parachain Id +pub fn child_para_account_id(para: u32) -> relay_chain::AccountId { + let location = (Parachain(para),); + relay_chain::LocationToAccountId::convert(location.into()).unwrap() +} + +/// Derive parachain sovereign account on a sibling parachain, from parachain Id +pub fn sibling_para_account_id(para: u32) -> parachain::AccountId { + let location = (Parent, X1(Parachain(para))); + parachain::LocationToAccountId::convert(location.into()).unwrap() +} + +/// Derive parachain's account's account on a sibling parachain +pub fn sibling_para_account_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> parachain::AccountId { + let location = ( + Parent, + Parachain(para), + AccountId32 { + // we have kusama as relay in mock + network: Some(Kusama), + id: who.into(), + }, + ); + parachain::LocationToAccountId::convert(location.into()).unwrap() +} + +/// Prepare parachain test externality +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (sibling_para_account_account_id(1, ALICE), INITIAL_BALANCE), + (sibling_para_account_account_id(2, ALICE), INITIAL_BALANCE), + (sibling_para_account_id(1), INITIAL_BALANCE), + (sibling_para_account_id(2), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + + parachain::DappsStaking::on_initialize(1); + let (staker_rewards, dev_rewards) = issue_dapps_staking_rewards(); + parachain::DappsStaking::rewards(staker_rewards, dev_rewards); + }); + ext +} + +/// Prepare relay chain test externality +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (child_para_account_id(1), INITIAL_BALANCE), + (child_para_account_id(2), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// Issues and returns negative imbalances of (staker rewards, developer rewards) +fn issue_dapps_staking_rewards() -> (parachain::NegativeImbalance, parachain::NegativeImbalance) { + ( + parachain::Balances::issue(DAPP_STAKER_REWARD_PER_BLOCK), + parachain::Balances::issue(DAPP_STAKER_DEV_PER_BLOCK), + ) +} + +/// Register and configure the asset for use in XCM +/// It first create the asset in `pallet_assets` and then register the asset multilocation +/// mapping in `pallet_xc_asset_config`, and lastly set the asset per second for calculating +/// XCM execution cost (only applicable if `is_sufficent` is true) +pub fn _register_and_setup_xcm_asset( + origin: Runtime::RuntimeOrigin, + // AssetId for the new asset + asset_id: AssetId, + // Asset multilocation + asset_location: impl Into + Clone, + // Asset controller + asset_controller: ::Source, + // make asset payable, default true + is_sufficent: Option, + // minimum balance for account to exist (ED), default, 0 + min_balance: Option, + // Asset unit per second for calculating execution cost for XCM, default 1_000_000_000_000 + units_per_second: Option, +) -> DispatchResult +where + Runtime: pallet_xc_asset_config::Config + pallet_assets::Config, + AssetId: IsType<::AssetId> + + IsType<::AssetId> + + Clone, +{ + // Register the asset + pallet_assets::Pallet::::force_create( + origin.clone(), + ::AssetIdParameter::from(asset_id.clone().into()), + asset_controller, + is_sufficent.unwrap_or(true), + min_balance.unwrap_or(Bounded::min_value()), + )?; + + // Save the asset and multilocation mapping + pallet_xc_asset_config::Pallet::::register_asset_location( + origin.clone(), + Box::new(asset_location.clone().into().into_versioned()), + asset_id.into(), + )?; + + // set the units per second for XCM cost + pallet_xc_asset_config::Pallet::::set_asset_units_per_second( + origin, + Box::new(asset_location.into().into_versioned()), + units_per_second.unwrap_or(1_000_000_000_000), + ) +} + +/// Load a given wasm module from wasm binary contents along +/// with it's hash. +/// +/// The fixture files are located under the `fixtures/` directory. +pub fn load_module( + fixture_name: &str, +) -> std::io::Result<(Vec, ::Output)> +where + T: frame_system::Config, +{ + let fixture_path = ["fixtures/", fixture_name, ".wasm"].concat(); + let wasm_binary = std::fs::read(fixture_path)?; + let code_hash = T::Hashing::hash(&wasm_binary); + Ok((wasm_binary, code_hash)) +} + +/// Load and deploy the contract from wasm binary +/// and check for successful deploy +pub fn deploy_contract( + contract_name: &str, + origin: T::AccountId, + value: ContractBalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + data: Vec, +) -> (T::AccountId, ::Output) { + let (code, hash) = load_module::(contract_name).unwrap(); + let outcome = pallet_contracts::Pallet::::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + Code::Upload(code), + data, + // vec![], + vec![], + true, + ); + + // make sure it does not revert + let result = outcome.result.unwrap(); + assert!( + !result.result.did_revert(), + "deploy_contract: reverted - {:?}", + result + ); + (result.account_id, hash) +} + +pub fn call_contract_method( + origin: T::AccountId, + dest: T::AccountId, + value: ContractBalanceOf, + gas_limit: Weight, + storage_deposit_limit: Option>, + data: Vec, + debug: bool, +) -> (V, ReturnFlags, Weight) { + let outcome = pallet_contracts::Pallet::::bare_call( + origin, + dest, + value, + gas_limit, + storage_deposit_limit, + data, + debug, + Determinism::Deterministic, + ); + + if debug { + println!( + "Contract debug - {:?}", + String::from_utf8(outcome.debug_message.clone()) + ); + } + + let res = outcome.result.unwrap(); + // check for revert + assert!(!res.did_revert(), "Contract reverted!"); + + ( + V::decode(&mut res.data.as_ref()).unwrap(), + res.flags, + outcome.gas_consumed, + ) +} diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/msg_queue.rs b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/msg_queue.rs new file mode 100644 index 00000000..437034eb --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/msg_queue.rs @@ -0,0 +1,189 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use frame_support::weights::Weight; +use parity_scale_codec::{Decode, Encode}; +use sp_runtime::traits::Hash; +use sp_std::prelude::*; + +use polkadot_core_primitives::BlockNumber as RelayBlockNumber; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, +}; +use xcm::{latest::prelude::*, VersionedXcm}; + +#[frame_support::pallet] +pub mod mock_msg_queue { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_dmp)] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn received_xcmp)] + /// A queue of received XCMP messages + pub(super) type ReceivedXcmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let message_hash = Encode::using_encoded(&xcm, sp_io::hashing::blake2_256); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + let location = (Parent, Parachain(sender.into())); + >::append(xcm.clone()); + match T::XcmExecutor::execute_xcm( + location, + xcm.clone(), + message_hash, + max_weight, + ) { + Outcome::Error(e) => { + println!("Error in XCMP handling: {:?}, sender=Parachain({sender}), xcm={xcm:?}", e); + (Err(e.clone()), Event::Fail(Some(hash), e)) + } + Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), + // As far as the caller is concerned, this was dispatched without error, so + // we just report the weight used. + Outcome::Incomplete(w, e) => { + println!("Incomplete XCMP handling: {:?}, {sender}", e); + (Ok(w), Event::Fail(Some(hash), e)) + } + } + } + Err(()) => ( + Err(XcmError::UnhandledXcmVersion), + Event::BadVersion(Some(hash)), + ), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + } + Ok(versioned) => match Xcm::try_from(versioned) { + Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), + Ok(x) => { + let outcome = T::XcmExecutor::execute_xcm(Parent, x.clone(), id, limit); + >::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + } + }, + } + } + limit + } + } +} diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/parachain.rs b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/parachain.rs new file mode 100644 index 00000000..057edb4f --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/parachain.rs @@ -0,0 +1,538 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + match_types, parameter_types, + traits::{ + AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, Currency, Everything, Imbalance, + Nothing, OnUnbalanced, + }, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureSigned, +}; +use pallet_contracts::chain_extension::RegisteredChainExtension; +use pallet_xcm_transactor::chain_extension::{XCMExtension, XCM_EXTENSION_ID}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use polkadot_core_primitives::BlakeTwo256; +use sp_core::{ConstBool, H256}; +use sp_runtime::{ + generic::Header, + traits::{AccountIdConversion, Convert, IdentityLookup}, + AccountId32, Perbill, RuntimeDebug, +}; +use sp_std::prelude::*; + +use super::msg_queue::*; +use xcm::latest::prelude::{AssetId as XcmAssetId, *}; +use xcm_builder::{ + Account32Hash, AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ConvertedConcreteId, CurrencyAdapter, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, + NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, WithComputedOrigin, +}; +use xcm_executor::{traits::JustTry, XcmExecutor}; + +use xcm_primitives::{ + AssetLocationIdConverter, FixedRateOfForeignAsset, ReserveAssetFilter, XcmFungibleFeeHandler, +}; + +pub type AccountId = AccountId32; +pub type Balance = u128; +pub type AssetId = u128; + +pub type NegativeImbalance = >::NegativeImbalance; + +pub type ShidenAssetLocationIdConverter = AssetLocationIdConverter; + +parameter_types! { + pub const BlockHashCount: u32 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u32; + type Hash = H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = AssetId; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<10>; + type MetadataDepositPerByte = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type ApprovalDeposit = ConstU128<10>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type RemoveItemsLimit = ConstU32<100>; + type CallbackHandle = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +impl pallet_insecure_randomness_collective_flip::Config for Runtime {} + +/// Constant values used within the runtime. +pub const MICROSDN: Balance = 1_000_000_000_000; +pub const MILLISDN: Balance = 1_000 * MICROSDN; +/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers. +/// This is used to limit the maximal weight of a single extrinsic. +const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); +/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used +/// by Operational extrinsics. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 0.5 seconds of compute with a 6 second average block time. +const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_div(2), + polkadot_primitives::MAX_POV_SIZE as u64, +); + +parameter_types! { + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub SS58Prefix: u8 = 5; +} + +// TODO: changing depost per item and per byte to `deposit` function will require storage migration it seems +parameter_types! { + pub const DepositPerItem: Balance = MILLISDN / 1_000_000; + pub const DepositPerByte: Balance = MILLISDN / 1_000_000; + // The lazy deletion runs inside on_initialize. + pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * + RuntimeBlockWeights::get().max_block; + pub Schedule: pallet_contracts::Schedule = Default::default(); +} + +impl Convert for Runtime { + fn convert(w: Weight) -> Balance { + w.ref_time().into() + } +} + +impl pallet_contracts::Config for Runtime { + type Time = Timestamp; + type Randomness = Randomness; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + /// The safest default is to allow no calls at all. + /// + /// Runtimes should whitelist dispatchables that are allowed to be called from contracts + /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to + /// change because that would break already deployed contracts. The `Call` structure itself + /// is not allowed to change the indices of existing pallets, too. + type CallFilter = Nothing; + type DepositPerItem = DepositPerItem; + type DepositPerByte = DepositPerByte; + type CallStack = [pallet_contracts::Frame; 5]; + /// We are not using the pallet_transaction_payment for simplicity + type WeightPrice = Self; + type WeightInfo = pallet_contracts::weights::SubstrateWeight; + type ChainExtension = XCMExtension>; + type DeletionQueueDepth = ConstU32<128>; + type DeletionWeightLimit = DeletionWeightLimit; + type Schedule = Schedule; + type AddressGenerator = pallet_contracts::DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = ConstBool; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; +} + +pub struct BurnFees; +impl OnUnbalanced for BurnFees { + /// Payout tips but burn all the fees + fn on_unbalanceds(mut fees_then_tips: impl Iterator) { + if let Some(mut fees_to_burn) = fees_then_tips.next() { + if let Some(tips) = fees_then_tips.next() { + fees_to_burn.subsume(tips) + } + drop(fees_to_burn); + } + } +} + +#[derive( + PartialEq, Eq, Copy, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, scale_info::TypeInfo, +)] +pub enum SmartContract { + Wasm(u32), +} + +impl Default for SmartContract { + fn default() -> Self { + SmartContract::Wasm(0) + } +} + +parameter_types! { + pub const DappsStakingPalletId: PalletId = PalletId(*b"py/dpsst"); + pub const MaxUnlockingChunks: u32 = 5; + pub const UnbondingPeriod: u32 = 5; + pub const MaxEraStakeValues: u32 = 5; +} + +impl pallet_dapps_staking::Config for Runtime { + type Currency = Balances; + type BlockPerEra = ConstU32<5>; + type SmartContract = SmartContract; + type RegisterDeposit = ConstU128<1>; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_dapps_staking::weights::SubstrateWeight; + type MaxNumberOfStakersPerContract = ConstU32<8>; + type MinimumStakingAmount = ConstU128<1>; + type PalletId = DappsStakingPalletId; + type MinimumRemainingAmount = ConstU128<0>; + type MaxUnlockingChunks = ConstU32<4>; + type UnbondingPeriod = ConstU32<2>; + type MaxEraStakeValues = ConstU32<4>; + type UnregisteredDappRewardRetention = ConstU32<7>; +} + +parameter_types! { + pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub TreasuryAccountId: AccountId = TreasuryPalletId::get().into_account_truncating(); +} + +impl pallet_xc_asset_config::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AssetId = AssetId; + type XcAssetChanged = (); + type ManagerOrigin = frame_system::EnsureRoot; + type WeightInfo = pallet_xc_asset_config::weights::SubstrateWeight; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); + pub const ReservedDmpWeight: Weight = Weight::from_ref_time(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4)); +} + +parameter_types! { + pub RelayNetwork: Option = Some(NetworkId::Kusama); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(MsgQueue::parachain_id().into())); + pub const ShidenLocation: MultiLocation = Here.into_location(); + pub DummyCheckingAccount: AccountId = PolkadotXcm::check_account(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the default `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, + // Derives a private `Account32` by hashing `("multiloc", received multilocation)` + Account32Hash, +); + +/// Means for transacting the native currency on this chain. +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// Means for transacting assets besides the native currency on this chain. +pub type FungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + Assets, + // Use this currency when it is a fungible asset matching the given location or name: + ConvertedConcreteId, + // Convert an XCM MultiLocation into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Assets`. + NoChecking, + // We don't track any teleports of `Assets`. + DummyCheckingAccount, +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = (CurrencyTransactor, FungiblesTransactor); + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognised. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognised. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + pallet_xcm::XcmPassthrough, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, +); + +parameter_types! { + pub const UnitWeightCost: Weight = Weight::from_ref_time(10); + pub const MaxInstructions: u32 = 100; + pub NativePerSecond: (XcmAssetId, u128, u128) = (Concrete(ShidenLocation::get()), 1_000_000_000_000, 1024 * 1024); +} + +pub type XcmRouter = super::ParachainXcmRouter; + +match_types! { + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; +} + +pub type XcmBarrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + // This will first calculate the derived origin, before checking it against the barrier implementation + WithComputedOrigin, UniversalLocation, ConstU32<8>>, + // Parent and its plurality get free execution + AllowUnpaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, +); + +// Used to handle XCM fee deposit into treasury account +pub type ShidenXcmFungibleFeeHandler = XcmFungibleFeeHandler< + AccountId, + ConvertedConcreteId, + Assets, + TreasuryAccountId, +>; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + type IsReserve = ReserveAssetFilter; + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = XcmBarrier; + type Weigher = FixedWeightBounds; + type Trader = ( + FixedRateOfFungible, + FixedRateOfForeignAsset, + ); + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = ConstU32<64>; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parachain(1000).into()); +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<0>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +parameter_types! { + pub const CallbackGasLimit: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); +} + +impl pallet_xcm_transactor::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type CallbackHandler = XcmTransact; + type RegisterQueryOrigin = EnsureXcmOrigin; + type MaxCallbackWeight = CallbackGasLimit; + type WeightInfo = pallet_xcm_transactor::SubstrateWeight; +} + +impl RegisteredChainExtension + for XCMExtension +{ + const ID: u16 = XCM_EXTENSION_ID; +} + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + Assets: pallet_assets::{Pallet, Call, Storage, Event}, + XcAssetConfig: pallet_xc_asset_config::{Pallet, Call, Storage, Event}, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin}, + DappsStaking: pallet_dapps_staking::{Pallet, Call, Event}, + Randomness: pallet_insecure_randomness_collective_flip::{Pallet, Storage}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, + XcmTransact: pallet_xcm_transactor::{Pallet, Call, Storage, Event}, + } +); diff --git a/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/relay_chain.rs b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/relay_chain.rs new file mode 100644 index 00000000..1e2cbd98 --- /dev/null +++ b/frame/pallet-xcm-transactor/xcm-simulator/src/mocks/relay_chain.rs @@ -0,0 +1,219 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar 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. + +// Astar 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 Astar. If not, see . + +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU32, Everything, Nothing}, + weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared, ump}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, +}; +use xcm_executor::XcmExecutor; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl shared::Config for Runtime {} + +impl configuration::Config for Runtime { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub const KsmLocation: MultiLocation = Here.into_location(); + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub UniversalLocation: InteriorMultiLocation = Here; +} + +pub type SovereignAccountOf = ( + ChildParachainConvertsVia, + AccountId32Aliases, +); + +pub type LocalAssetTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = Weight::from_ref_time(1_000); + pub KsmPerSecond: (AssetId, u128, u128) = (Concrete(KsmLocation::get()), 1, 1024 * 1024); + pub const MaxInstructions: u32 = 100; +} + +pub type XcmRouter = super::RelayChainXcmRouter; +pub type Barrier = AllowUnpaidExecutionFrom; + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); + type AssetTrap = (); + type AssetClaims = (); + type SubscriptionService = (); + + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = ConstU32<64>; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = (); + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +pub type LocationToAccountId = (ChildParachainConvertsVia,); + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub ReachableDest: Option = Some(Parent.into()); +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally... + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<0>; + type WeightInfo = pallet_xcm::TestWeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type ReachableDest = ReachableDest; +} + +parameter_types! { + pub const FirstMessageFactorPercent: u64 = 100; +} + +impl ump::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type UmpSink = ump::XcmSink, Runtime>; + type FirstMessageFactorPercent = FirstMessageFactorPercent; + type ExecuteOverweightOrigin = frame_system::EnsureRoot; + type WeightInfo = ump::TestWeightInfo; +} + +impl origin::Config for Runtime {} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + ParasOrigin: origin::{Pallet, Origin}, + ParasUmp: ump::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + } +);