From c2ca46e74820c1ced99658147ed70c10a8aa84cb Mon Sep 17 00:00:00 2001 From: Alistair Singh Date: Fri, 12 May 2023 17:28:31 +0200 Subject: [PATCH] Add statemine (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rustc grumbles * add second instance * leave some todos * fix AssetsToBlockAuthor * make it almost compile * make it actually compile * fix logical error * cargo fmt to resolve duplicate imports on merge * fix deps to make compile * fmt * (pallet-assets/pallet-asset-tx-payment) pathched with `sv-locked-for-gav-xcm-v3-and-bridges-plus-assets` * statemine progress * ForeignCreators :tada: * fix AssetFeeAsExistentialDepositMultiplier * no default instance * instantiate statemint * compile tests * Init of pallet with `transfer_asset_via_bridge` call Init of pallet with `transfer_asset_via_bridge` call Init of pallet with `transfer_asset_via_bridge` call - setup basic tests * Init of pallet with `transfer_asset_via_bridge` call * Box wrap inputs * Added withdraw/deposit to bridge's reserve account * TODO: temporary hack pipelines because of https://github.com/paritytech/devops/issues/2190 * Allow receive bridged Xcm::Trap on Westmint * Allow (temporary) Statemine send xcm messages * refactor TrustedBridgedNetworks * Allow (hacky) xcm::transact for remark/remark_with_event * Unit-test for Statemine - send_xcm_transact_with_remark_with_event_works * Fix for BridgedSignedAccountId32AsNative * don't rename pallet in runtime * don't rename and fix tests * Added `allowed_target_location` + reanchored assets/destination + tests * fix benchmark helper * fix benchmark build * more benchmark fixes * fix penpal and rococo * update BenchmarkHelper interface * fmt * Added weight `export_message` to statemint because of `pallet_xcm.send` * add foreign assets to westmint * add foreign assets to statemine * Governance can call `xcm:Transact` with bridges configuration management * Revert temporary disabled `scripts/ci/gitlab/pipeline/test.yml` * Unit-test for handling `ReserveAssetDeposited` on Westmint for ForeignAssets * use updated api for ensure origin trait * Benchmarks for pallet-bridge-assets-transfer (#2316) * benchmarks for pallet-bridge-asset-transfer * use proper template for pallet weights * fix test * fixing compialtion * fix (?) compilation warn/error in westmint * weight limits in can_governance_call_xcm_transact_with_bridge_assets_transfer_configuration * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_assets_transfer * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_assets_transfer * add prototype for multiple assets benchmarking --------- Co-authored-by: command-bot <> * Assets/ForeignAssets tests and fixes (#2167) * Test for create and transfer `TrustBackedAssets` with AssetTransactor * Test for transfer `local Currency` with AssetTransactor * Test for create foreign assets (covers foreign relaychain currency) * Added `ForeignFungiblesTransactor` and test for transfer `ForeignAssets` with AssetTransactor * Removed unused `pub const Local: MultiLocation` * Changed `ParaId -> Sibling` for `SiblingParachainConvertsVia` * Test for create foreign assets (covers local sibling parachain assets) * Reverted stuff for ForeignCreators from different global consensus (moved to transfer asset branch) * Refactor `weight_limit` for `execute_xcm` * Added test for `set_metadata` by ForeignCreator with `xcm::Transact(set_metadata)` * Renamed `receive_teleported_asset_works` -> `receive_teleported_asset_for_native_asset_works` * Allow `ForeignCreators` only for sibling parachains * Unify ReservedDmpWeight/ReservedXcmpWeight usage * Removed hack - replaced with `MatchedConvertedConcreteId` * Refactor `ForeignCreators` to assets-common * Add `ReceiveTeleportedAsset` test * Change test - `Utility::batch` -> Multiple `xcm::Transact` * Reusing the same deposits as for TrustBackedAssets * missing `try_successful_origin` ? * Finished `ForeignAssets` for westmint (converter, FungiblesApi, tests) * Refactoring tests - receive_teleported_asset_for_native_asset_works * ForeignAssets for statemine + refactored `receive_teleported_asset_from_foreign_creator_works` * Add `ForeignAssets` to statemine `FungiblesApi` * Add `asset_transactor_transfer_with_local_consensus_currency_works` to all runtimes * Added `asset_transactor_transfer_with_trust_backed_assets_works` test * Added `asset_transactor_transfer_with_foreign_assets_works` * Fix `missing `try_successful_origin` in implementation` * Added `create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works` * Added `ExpectTransactStatus` check * Small rename * Extended `test_assets_balances_api_works` with ForeignAssets for `statemine` * PR fixes * Update parachains/runtimes/assets/test-utils/src/test_cases.rs --------- Co-authored-by: parity-processbot <> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Added `StartsWithExplicitGlobalConsensus` to ignores (#2338) * Change to correct weight file for pallet_bridge_assets_transfer * Renamed `pallet-bridge-assets-transfer` to `pallet-bridge-transfer` * from_ref_time to from_parts * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_transfer * Added `ping_via_bridge` * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_transfer * Fix test * Revert not needed stuff * Added test-case `can_governance_change_bridge_transfer_configuration` * Added test `initiate_transfer_asset_via_bridge_for_native_asset_works` * Fix compilation * Small fixes * Added support for paid or unpaid execution by configuration * Removed stuff * Bump futures from 0.3.26 to 0.3.27 (#2359) Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.26 to 0.3.27. - [Release notes](https://github.com/rust-lang/futures-rs/releases) - [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.26...0.3.27) --- updated-dependencies: - dependency-name: futures dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sebastian Kunert * Bump clap from 4.1.11 to 4.1.13 (#2388) Bumps [clap](https://github.com/clap-rs/clap) from 4.1.11 to 4.1.13. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v4.1.11...v4.1.13) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Remove HeaderBackend from RelayChainRPCClient (#2385) * Remove HeaderBackend from RelayChainRPCClient * update lockfile for {"substrate", "polkadot"} --------- Co-authored-by: parity-processbot <> * Added `receive_reserve_asset_deposited_from_different_consensus_works` * Bump scale-info from 2.3.1 to 2.4.0 (#2386) Bumps [scale-info](https://github.com/paritytech/scale-info) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/paritytech/scale-info/releases) - [Changelog](https://github.com/paritytech/scale-info/blob/master/CHANGELOG.md) - [Commits](https://github.com/paritytech/scale-info/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: scale-info dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Renamed Bridges -> AllowedExporters * Bump serde_json from 1.0.94 to 1.0.95 (#2387) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.94 to 1.0.95. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.94...v1.0.95) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Companion: wasm-builder support stable Rust (#2393) * Companion: wasm-builder support stable Rust * update lockfile for {"polkadot", "substrate"} --------- Co-authored-by: parity-processbot <> * Added `IsReserve` handling for `ReserveAssetDeposited` + benchmarks * Bump thiserror from 1.0.38 to 1.0.40 (#2396) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.38 to 1.0.40. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.38...1.0.40) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix compile * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_transfer * Bump syn from 1.0.109 to 2.0.9 (#2397) Bumps [syn](https://github.com/dtolnay/syn) from 1.0.109 to 2.0.9. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/1.0.109...2.0.9) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump serde from 1.0.156 to 1.0.159 (#2395) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.156 to 1.0.159. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.156...v1.0.159) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Companion for https://github.com/paritytech/substrate/pull/13725 (#2401) * Companion for https://github.com/paritytech/substrate/pull/13725 * Add comment * update lockfile for {"substrate", "polkadot"} --------- Co-authored-by: parity-processbot <> * Bump syn from 2.0.9 to 2.0.11 (#2405) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.9 to 2.0.11. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.9...2.0.11) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump scale-info from 2.4.0 to 2.5.0 (#2404) Bumps [scale-info](https://github.com/paritytech/scale-info) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/paritytech/scale-info/releases) - [Changelog](https://github.com/paritytech/scale-info/blob/master/CHANGELOG.md) - [Commits](https://github.com/paritytech/scale-info/compare/v2.4.0...v2.5.0) --- updated-dependencies: - dependency-name: scale-info dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump tempfile from 3.4.0 to 3.5.0 (#2406) Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/Stebalien/tempfile/releases) - [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS) - [Commits](https://github.com/Stebalien/tempfile/commits) --- updated-dependencies: - dependency-name: tempfile dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: parity-processbot <> * bump zombienet version (#2411) * Fix conditional benchmarking * ".git/.scripts/commands/bench/bench.sh" pallet statemine assets pallet_bridge_transfer * ".git/.scripts/commands/bench/bench.sh" pallet westmint assets pallet_bridge_transfer * Bump tokio from 1.26.0 to 1.27.0 (#2413) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.26.0...tokio-1.27.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Change test weights * Bump syn from 2.0.11 to 2.0.12 (#2414) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.11 to 2.0.12. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.11...2.0.12) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump proc-macro2 from 1.0.52 to 1.0.54 (#2415) Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.52 to 1.0.54. - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.52...1.0.54) --- updated-dependencies: - dependency-name: proc-macro2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Companion PR for contract deletion updates (#2409) * Companion PR for contract deletion updates see https://github.com/paritytech/substrate/pull/13702 * Revert "Companion PR for contract deletion updates" This reverts commit 4fb2ca53a1bdfbd7dc0d35be52525da99547c76c. * fix lint * update lockfile for {"polkadot", "substrate"} --------- Co-authored-by: parity-processbot <> * Fix SafeCallFilter for westmint * Allow arbitrary key-values in RelayStateSproofBuilder (#2407) * help text examples + clean up (#2418) * Bump futures from 0.3.27 to 0.3.28 (#2420) Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.27 to 0.3.28. - [Release notes](https://github.com/rust-lang/futures-rs/releases) - [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.27...0.3.28) --- updated-dependencies: - dependency-name: futures dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump clap from 4.1.13 to 4.1.14 (#2421) Bumps [clap](https://github.com/clap-rs/clap) from 4.1.13 to 4.1.14. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v4.1.13...v4.1.14) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update Substrate & Polkadot (#2422) * Bump syn from 2.0.12 to 2.0.13 (#2428) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.12 to 2.0.13. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.12...2.0.13) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump hex-literal from 0.3.4 to 0.4.0 (#2426) Bumps [hex-literal](https://github.com/RustCrypto/utils) from 0.3.4 to 0.4.0. - [Release notes](https://github.com/RustCrypto/utils/releases) - [Commits](https://github.com/RustCrypto/utils/compare/hex-literal-v0.3.4...hex-literal-v0.4.0) --- updated-dependencies: - dependency-name: hex-literal dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Companion for #6986 (#2416) * refactor: apply substrate/pull/13610 * Added `origin` to config for `universal_origin` benchmark * update lockfile for {"polkadot", "substrate"} * Update --------- Co-authored-by: William Freudenberger Co-authored-by: parity-processbot <> Co-authored-by: Bastian Köcher * [backport] weights 9400 (#2425) * [benchmarks] pr with weights (#2373) Co-authored-by: paritytech-ci * [benchmarks] pr with weights (#2374) Co-authored-by: paritytech-ci * [benchmarks] pr with weights (#2375) Co-authored-by: paritytech-ci * Proof size in test wasn't sufficient (due to updated weights.) --------- Co-authored-by: Paritytech CI <52199148+paritytech-ci@users.noreply.github.com> Co-authored-by: paritytech-ci Co-authored-by: Giles Cope * Use send_xcm and add cost to event * Cleanup: Remove polkadot-service dependency from minimal node (#2430) * Remove polkadot-service dependency from minimal-node * Clean up error handline * Remove unwanted changes * Unused deps * Co #13699: Remove old calls (#2431) * Remove old calls Signed-off-by: Oliver Tale-Yazdi * update lockfile for {"substrate", "polkadot"} * Ignore warning in pallet ping Signed-off-by: Oliver Tale-Yazdi * Ignore more warnings... Signed-off-by: Oliver Tale-Yazdi * ... Signed-off-by: Oliver Tale-Yazdi --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> * [Backport] version bumps 9400 (#2424) * Bump crate versions * Bump spec_version to 9400 * bump transaction versions (#2364) * Refactor to support multiple MultiAsset + cleaning * ".git/.scripts/commands/bench/bench.sh" xcm westmint assets pallet_xcm_benchmarks::generic * Bump hex-literal from 0.4.0 to 0.4.1 (#2434) Bumps [hex-literal](https://github.com/RustCrypto/utils) from 0.4.0 to 0.4.1. - [Release notes](https://github.com/RustCrypto/utils/releases) - [Commits](https://github.com/RustCrypto/utils/compare/hex-literal-v0.4.0...hex-literal-v0.4.1) --- updated-dependencies: - dependency-name: hex-literal dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Updated bridge-hub-polkadot.json (#2435) * The Polkadot Fellowship import (#2236) * Fellowship into Collectives * cargo.lock * tracks alias * allow to send Fellows origin over XCM * update todos, remove duplication of type * use Collectives location for Fellows body * alias for ranks constants * benchmarks * proxy for Fellowship * docs * correct copyright date * Apply suggestions from code review Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * rustfmt * remove council, update origins * renames * remove tech committee from promote origin * renames * Fellowship import * test * rename mod * fix import * updated addresses (only ss58 version) * update addresses * doc nits * weights with new api * update addresses * fix try runtime * update addresses * use pallet api to import the members * merge fix * hex-literal version * add Bradley to the 1 rank --------- Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: parity-processbot <> * Companion for #13302 (#2357) * primitives/core: Derive scale_info::TypeInfo for runtime APIs Signed-off-by: Alexandru Vasile * parachains: Derive scale_info::TypeInfo for FungiblesAccessError Signed-off-by: Alexandru Vasile * parachains: Fix `TypeInfo` import path Signed-off-by: Alexandru Vasile * update lockfile for {"polkadot", "substrate"} * Adjust testing for the new API Signed-off-by: Alexandru Vasile * Adjust deprecated methods Signed-off-by: Alexandru Vasile --------- Signed-off-by: Alexandru Vasile Co-authored-by: parity-processbot <> * use stable rust toolchain in ci * Bump syn from 2.0.13 to 2.0.14 (#2446) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.13 to 2.0.14. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.13...2.0.14) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump serde from 1.0.159 to 1.0.160 (#2445) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.159 to 1.0.160. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.159...v1.0.160) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Invoke cargo build commands with `--locked` (#2444) * Bump actions/checkout from 3.1.0 to 3.5.1 (#2448) * Bump actions/checkout from 3.1.0 to 3.5.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.5.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.1.0...83b7061638ee4956cf7545a6f7efe594e5ad0247) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * align version with hash --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sergejs Kostjucenko * Updated doc * Bump serde_json from 1.0.95 to 1.0.96 (#2453) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.95 to 1.0.96. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.95...v1.0.96) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/checkout from 3.5.1 to 3.5.2 (#2452) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.1 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/83b7061638ee4956cf7545a6f7efe594e5ad0247...8e5e7e5ab8b370d6c329ec480221332ada57f0ab) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump syn from 2.0.14 to 2.0.15 (#2454) Bumps [syn](https://github.com/dtolnay/syn) from 2.0.14 to 2.0.15. - [Release notes](https://github.com/dtolnay/syn/releases) - [Commits](https://github.com/dtolnay/syn/compare/2.0.14...2.0.15) --- updated-dependencies: - dependency-name: syn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump assert_cmd from 2.0.10 to 2.0.11 (#2457) Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 2.0.10 to 2.0.11. - [Release notes](https://github.com/assert-rs/assert_cmd/releases) - [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md) - [Commits](https://github.com/assert-rs/assert_cmd/compare/v2.0.10...v2.0.11) --- updated-dependencies: - dependency-name: assert_cmd dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Changed `query_account_balances` return type (#2455) * Companion for substrate#13883 (#2460) * update substrate * update lockfile for {"polkadot", "substrate"} --------- Co-authored-by: parity-processbot <> * Optimize level monitor reconstruction (#2461) * Optimize level monitor reconstruction * Fix counter increment and test * Struct comments as doc comments * Bump clap from 4.1.14 to 4.2.3 (#2465) * Bump Swatinem/rust-cache from 2.2.0 to 2.2.1 (#2456) Bumps [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache) from 2.2.0 to 2.2.1. - [Release notes](https://github.com/Swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](https://github.com/Swatinem/rust-cache/compare/359a70e43a0bb8a13953b04a90f76428b4959bb6...6fd3edff6979b79f87531400ad694fb7f2c84b1f) --- updated-dependencies: - dependency-name: Swatinem/rust-cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Companion for substrate#13771 (#2410) * max proposal weight config * update deps --------- Co-authored-by: parity-processbot <> * Parachain node should not recover blocks while syncing (#2462) * Address review comments * [Polkadot Companion] for 7101 (#2470) * [Polkadot Companion] for 7101 PR: https://github.com/paritytech/polkadot/pull/7101 * update lockfile for {"polkadot", "substrate"} --------- Co-authored-by: parity-processbot <> * Align BridgeHub runtimes with other SP runtimes + reused test for teleport native tokens + some nits (#2449) * Align BridgeHub runtimes with other SP runtimes * Reused `teleports_for_native_asset_works` test to all bridge-hub runtime * Fix import vs doc * Removed unnecessery deps * DealWithFees + ToAuthor->ToStakingPot for BH according to the other runtimes * Update parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update parachains/runtimes/bridge-hubs/bridge-hub-polkadot/src/xcm_config.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update parachains/runtimes/bridge-hubs/bridge-hub-kusama/src/xcm_config.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Align all desc * Extract runtime_para_id for test * Fix test --------- Co-authored-by: parity-processbot <> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Extract runtime_para_id for test * Typos * Added helper for `execute_as_governance` * Fix test because `UnpaidRemoteExporter` adds now `UnpaidExecution` instruction * Use `execute_as_governance` function * typos * modified lock * revert to parity/master --------- Signed-off-by: dependabot[bot] Signed-off-by: Oliver Tale-Yazdi Signed-off-by: Alexandru Vasile Co-authored-by: joepetrowski Co-authored-by: Branislav Kontur Co-authored-by: parity-processbot <> Co-authored-by: muharem Co-authored-by: Svyatoslav Nikolsky Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sebastian Kunert Co-authored-by: Bastian Köcher Co-authored-by: Aaro Altonen <48052676+altonen@users.noreply.github.com> Co-authored-by: Javier Viola Co-authored-by: PG Herveou Co-authored-by: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> Co-authored-by: William Freudenberger Co-authored-by: Bastian Köcher Co-authored-by: Egor_P Co-authored-by: Paritytech CI <52199148+paritytech-ci@users.noreply.github.com> Co-authored-by: paritytech-ci Co-authored-by: Giles Cope Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Mira Ressel Co-authored-by: Sergejs Kostjucenko Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: Davide Galassi Co-authored-by: Serban Iorga Co-authored-by: Marcin S --- Cargo.lock | 26 + Cargo.toml | 1 + parachains/pallets/bridge-transfer/Cargo.toml | 58 + .../bridge-transfer/src/benchmarking.rs | 199 ++ .../pallets/bridge-transfer/src/impls.rs | 56 + parachains/pallets/bridge-transfer/src/lib.rs | 1594 +++++++++++++++++ .../pallets/bridge-transfer/src/weights.rs | 87 + parachains/runtimes/assets/common/src/lib.rs | 1 + .../assets/common/src/location_conversion.rs | 152 ++ .../runtimes/assets/common/src/matching.rs | 22 + .../runtimes/assets/statemine/Cargo.toml | 6 + .../runtimes/assets/statemine/src/lib.rs | 27 +- .../assets/statemine/src/weights/mod.rs | 1 + .../src/weights/pallet_bridge_transfer.rs | 175 ++ .../assets/statemine/src/xcm_config.rs | 113 +- .../runtimes/assets/statemine/tests/tests.rs | 52 +- .../runtimes/assets/test-utils/Cargo.toml | 8 +- .../runtimes/assets/test-utils/src/lib.rs | 41 +- .../assets/test-utils/src/test_cases.rs | 791 +++++++- .../runtimes/assets/westmint/Cargo.toml | 4 + .../runtimes/assets/westmint/src/lib.rs | 39 +- .../assets/westmint/src/weights/mod.rs | 1 + .../src/weights/pallet_bridge_transfer.rs | 153 ++ .../assets/westmint/src/weights/xcm/mod.rs | 2 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 134 +- .../assets/westmint/src/xcm_config.rs | 81 +- .../runtimes/assets/westmint/tests/tests.rs | 40 +- .../bridge-hub-rococo/tests/tests.rs | 26 + .../bridge-hubs/test-utils/src/test_cases.rs | 2 + scripts/generate_hex_encoded_call/index.js | 16 +- scripts/scale_encode_genesis/index.js | 6 +- 31 files changed, 3805 insertions(+), 109 deletions(-) create mode 100644 parachains/pallets/bridge-transfer/Cargo.toml create mode 100644 parachains/pallets/bridge-transfer/src/benchmarking.rs create mode 100644 parachains/pallets/bridge-transfer/src/impls.rs create mode 100644 parachains/pallets/bridge-transfer/src/lib.rs create mode 100644 parachains/pallets/bridge-transfer/src/weights.rs create mode 100644 parachains/runtimes/assets/common/src/location_conversion.rs create mode 100644 parachains/runtimes/assets/statemine/src/weights/pallet_bridge_transfer.rs create mode 100644 parachains/runtimes/assets/westmint/src/weights/pallet_bridge_transfer.rs diff --git a/Cargo.lock b/Cargo.lock index c21968959ab..1145e2dfb70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,6 +404,7 @@ dependencies = [ "hex-literal 0.3.4", "pallet-assets", "pallet-balances", + "pallet-bridge-transfer", "pallet-collator-selection", "pallet-session", "pallet-xcm", @@ -6856,6 +6857,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-bridge-transfer" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-xcmp-queue", + "frame-benchmarking", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "log", + "pallet-balances", + "parity-scale-codec", + "polkadot-parachain", + "scale-info", + "sp-runtime", + "sp-std", + "sp-version", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "pallet-child-bounties" version = "4.0.0-dev" @@ -13497,6 +13520,7 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bridge-transfer", "pallet-collator-selection", "pallet-multisig", "pallet-nfts", @@ -13524,6 +13548,7 @@ dependencies = [ "sp-consensus-aura", "sp-core", "sp-inherents", + "sp-io", "sp-offchain", "sp-runtime", "sp-session", @@ -15573,6 +15598,7 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bridge-transfer", "pallet-collator-selection", "pallet-multisig", "pallet-nfts", diff --git a/Cargo.toml b/Cargo.toml index 4ceb8d5c04a..ae2966e05a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "primitives/utility", "polkadot-parachain", "parachains/common", + "parachains/pallets/bridge-transfer", "parachains/pallets/parachain-info", "parachains/pallets/ping", "parachains/runtimes/testing/rococo-parachain", diff --git a/parachains/pallets/bridge-transfer/Cargo.toml b/parachains/pallets/bridge-transfer/Cargo.toml new file mode 100644 index 00000000000..505949daa50 --- /dev/null +++ b/parachains/pallets/bridge-transfer/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "pallet-bridge-transfer" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://docs.substrate.io/" +repository = "https://github.com/paritytech/cumulus/" +description = "Pallet for message transfer through bridges" +readme = "README.md" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.3.0", default-features = false, features = ["derive"] } +log = { version = "0.4.14", default-features = false } + +# Substrate +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", optional = true, default-features = false, branch = "master" } +frame-system-benchmarking = { git = "https://github.com/paritytech/substrate", optional = true, default-features = false, branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +# Polkadot +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +[dev-dependencies] +sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" } +cumulus-pallet-xcmp-queue = { path = "../../../pallets/xcmp-queue" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + "sp-std/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "xcm/std", + "xcm-builder/std", + "xcm-executor/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/parachains/pallets/bridge-transfer/src/benchmarking.rs b/parachains/pallets/bridge-transfer/src/benchmarking.rs new file mode 100644 index 00000000000..15adcb2fc42 --- /dev/null +++ b/parachains/pallets/bridge-transfer/src/benchmarking.rs @@ -0,0 +1,199 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! `BridgeTransfer` pallet benchmarks. + +use crate::{ + AllowedExporters, AllowedReserveLocations, AllowedUniversalAliases, BenchmarkHelper, Call, + Config, Event, Pallet, PingMessageBuilder, +}; + +use frame_benchmarking::{benchmarks, BenchmarkError}; +use frame_support::{ + ensure, + traits::{EnsureOrigin, Get}, +}; +use sp_std::prelude::*; +use xcm::prelude::*; + +#[cfg(feature = "runtime-benchmarks")] +impl Pallet { + #[cfg(feature = "runtime-benchmarks")] + pub fn insert_universal_alias_for_benchmarks((location, junction): (MultiLocation, Junction)) { + assert!(matches!( + AllowedUniversalAliases::::try_mutate(location, |junctions| junctions + .try_insert(junction)), + Ok(true) + )); + } +} + +benchmarks! { + transfer_asset_via_bridge { + let _ = T::TransferAssetOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + // every asset has its own configuration and ledger, so there's a performance dependency + // (be sure to use "worst" of assets) + let max_assets_limit = T::MaxAssetsLimit::get(); + ensure!(max_assets_limit > 0, "MaxAssetsLimit not set up correctly."); + let (bridged_network, bridge_config) = T::BenchmarkHelper::bridge_config() + .ok_or(BenchmarkError::Stop("missing `bridge_config` data"))?; + let (origin, assets, destination) = T::BenchmarkHelper::prepare_asset_transfer() + .ok_or(BenchmarkError::Stop("missing `prepare_asset_transfer` data"))?; + let assets_count = match &assets { + VersionedMultiAssets::V2(assets) => assets.len(), + VersionedMultiAssets::V3(assets) => assets.len(), + }; + ensure!(assets_count == max_assets_limit as usize, "`assets` not set up correctly for worst case."); + AllowedExporters::::insert(bridged_network, bridge_config); + }: _(origin, Box::new(assets), Box::new(destination)) + verify { + // we don't care about message hash or sender cost here, just check that the transfer has been initiated + let actual_event = frame_system::Pallet::::events().pop().map(|r| r.event); + let expected_event: ::RuntimeEvent = Event::TransferInitiated { + message_hash: Default::default(), + sender_cost: Default::default(), + }.into(); + assert!(matches!(actual_event, Some(expected_event))); + } + + ping_via_bridge { + let _ = T::TransferPingOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + let (bridged_network, bridge_config) = T::BenchmarkHelper::bridge_config() + .ok_or(BenchmarkError::Stop("missing `bridge_config` data"))?; + AllowedExporters::::insert(bridged_network, bridge_config); + + let (origin, destination) = T::BenchmarkHelper::prepare_ping_transfer() + .ok_or(BenchmarkError::Stop("missing `prepare_ping_transfer` data"))?; + + let origin_location = T::TransferPingOrigin::ensure_origin(origin.clone()).map_err(|_| BenchmarkError::Stop("invalid `origin`"), + )?; + let (_, _, destination_location) = Pallet::::ensure_remote_destination(destination.clone()).map_err(|_| + BenchmarkError::Stop("invalid `destination_location`"), + )?; + let _ = T::PingMessageBuilder::try_build(&origin_location, &bridged_network, &destination_location).ok_or( + BenchmarkError::Stop("invalid `PingMessageBuilder`"), + )?; + }: _(origin, Box::new(destination)) + verify { + // we don't care about message hash or sender cost here, just check that the transfer has been initiated + let actual_event = frame_system::Pallet::::events().pop().map(|r| r.event); + let expected_event: ::RuntimeEvent = Event::TransferInitiated { + message_hash: Default::default(), + sender_cost: Default::default(), + }.into(); + assert!(matches!(actual_event, Some(expected_event))); + } + + add_exporter_config { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (bridged_network, bridge_config) = T::BenchmarkHelper::bridge_config() + .ok_or(BenchmarkError::Stop("missing `bridge_config` data"))?; + }: _(origin, bridged_network, Box::new(bridge_config.clone())) + verify { + assert_eq!(AllowedExporters::::get(bridged_network), Some(bridge_config)); + } + + remove_exporter_config { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (bridged_network, bridge_config) = T::BenchmarkHelper::bridge_config() + .ok_or(BenchmarkError::Stop("missing `bridge_config` data"))?; + AllowedExporters::::insert(bridged_network, bridge_config); + }: _(origin, bridged_network) + verify { + assert_eq!(AllowedExporters::::get(bridged_network), None); + } + + update_exporter_config { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (bridged_network, bridge_config) = T::BenchmarkHelper::bridge_config() + .ok_or(BenchmarkError::Stop("missing `bridge_config` data"))?; + AllowedExporters::::insert(bridged_network, bridge_config); + + let bridge_location_fee = None; + let target_location_fee = Some(xcm::VersionedMultiAsset::V3(MultiAsset { + id: Concrete(MultiLocation::parent()), + fun: Fungible(1_000_0000), + })); + }: _(origin, bridged_network, bridge_location_fee.clone().map(Box::new), target_location_fee.clone().map(Box::new)) + verify { + let exporter = AllowedExporters::::get(bridged_network).unwrap(); + assert_eq!(exporter.bridge_location_fee, bridge_location_fee.map(|fee| MultiAsset::try_from(fee).unwrap())); + assert_eq!(exporter.max_target_location_fee, target_location_fee.map(|fee| MultiAsset::try_from(fee).unwrap())); + } + + add_universal_alias { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (location, junction) = match T::BenchmarkHelper::universal_alias() { + Some(alias) => alias, + None => match T::UniversalAliasesLimit::get() > 0_u32 { + true => return Err(BenchmarkError::Stop("missing `universal_alias` data")), + false => return Err(BenchmarkError::Weightless), + } + }; + }: _(origin, Box::new(location.clone()), junction) + verify { + assert!(AllowedUniversalAliases::::get(&location.try_as().unwrap()).contains(&junction)); + } + + remove_universal_alias { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (location, junction) = match T::BenchmarkHelper::universal_alias() { + Some(alias) => alias, + None => match T::UniversalAliasesLimit::get() > 0_u32 { + true => return Err(BenchmarkError::Stop("missing `universal_alias` data")), + false => return Err(BenchmarkError::Weightless), + } + }; + Pallet::::insert_universal_alias_for_benchmarks((location.clone().try_into().unwrap(), junction)); + }: _(origin, Box::new(location.clone()), vec![junction.clone()]) + verify { + assert!(!AllowedUniversalAliases::::get(&location.try_as().unwrap()).contains(&junction)); + } + + add_reserve_location { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let location = match T::BenchmarkHelper::reserve_location() { + Some(location) => location, + None => match T::ReserveLocationsLimit::get() > 0_u32 { + true => return Err(BenchmarkError::Stop("missing `reserve_location` data")), + false => return Err(BenchmarkError::Weightless), + } + }; + }: _(origin, Box::new(location.clone())) + verify { + assert!(AllowedReserveLocations::::get().contains(&location.try_as().unwrap())); + } + + remove_reserve_location { + let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let location = match T::BenchmarkHelper::reserve_location() { + Some(location) => location, + None => match T::ReserveLocationsLimit::get() > 0_u32 { + true => return Err(BenchmarkError::Stop("missing `reserve_location` data")), + false => return Err(BenchmarkError::Weightless), + } + }; + let multilocation: MultiLocation = location.clone().try_into().unwrap(); + assert!(AllowedReserveLocations::::try_mutate(|locations| locations.try_insert(multilocation)).unwrap()); + }: _(origin, vec![location.clone()]) + verify { + assert!(!AllowedReserveLocations::::get().contains(&location.try_as().unwrap())); + } + + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::TestRuntime); +} diff --git a/parachains/pallets/bridge-transfer/src/impls.rs b/parachains/pallets/bridge-transfer/src/impls.rs new file mode 100644 index 00000000000..65b6adb1d79 --- /dev/null +++ b/parachains/pallets/bridge-transfer/src/impls.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Config, Pallet}; +use frame_support::traits::{Contains, ContainsPair}; +use xcm::prelude::*; +use xcm_builder::ExporterFor; + +/// `ExporterFor` implementation to check if we can transfer anything to `NetworkId` +impl ExporterFor for Pallet { + fn exporter_for( + network: &NetworkId, + _remote_location: &InteriorMultiLocation, + _message: &Xcm<()>, + ) -> Option<(MultiLocation, Option)> { + Self::allowed_exporters(network) + .map(|bridge_config| (bridge_config.bridge_location, bridge_config.bridge_location_fee)) + } +} + +/// Verifies if we have `(MultiLocation, Junction)` in allowed universal aliases. +pub struct AllowedUniversalAliasesOf(sp_std::marker::PhantomData); +impl Contains<(MultiLocation, Junction)> for AllowedUniversalAliasesOf { + fn contains((location, junction): &(MultiLocation, Junction)) -> bool { + log::trace!(target: "xcm::contains", "AllowedUniversalAliasesOf location: {:?}, junction: {:?}", location, junction); + Pallet::::allowed_universal_aliases(location).contains(junction) + } +} + +/// Verifies if we can allow `(MultiAsset, MultiLocation)` as trusted reserve. +pub struct IsAllowedReserveOf(sp_std::marker::PhantomData<(T, F)>); +impl> ContainsPair + for IsAllowedReserveOf +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "IsAllowedReserveOf asset: {:?}, origin: {:?}", asset, origin); + // first check - if we have configured origin as trusted reserve location + if !Pallet::::allowed_reserve_locations().contains(origin) { + return false + } + // second check - we need to pass additional `(asset, origin)` filter + F::contains(asset, origin) + } +} diff --git a/parachains/pallets/bridge-transfer/src/lib.rs b/parachains/pallets/bridge-transfer/src/lib.rs new file mode 100644 index 00000000000..f5488e8ba74 --- /dev/null +++ b/parachains/pallets/bridge-transfer/src/lib.rs @@ -0,0 +1,1594 @@ +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Bridge Transfer Pallet +//! +//! Module which could help with different transfers through bridges, +//! e.g. move assets between different global consensus... +//! +//! ## Overview +//! +//! Pallet supports configuration for two independent scenarios: +//! +//! ### Transfer out +//! +//! * see (Config for transfer out) in the code +//! * if you want to allow initiate bridge transfer from runtime, +//! actually pallet supports asset transfer and ping with dedicated extrinsics `transfer_asset_via_bridge` / `ping_via_bridge` +//! * e.g. for asset transfer with correct configuration it sends `ReserveAssetDeposited` over bridge, +//! you can configure bridge location and allowed target location with `AllowedExporters` +//! +//! ### Transfer in +//! +//! * see (Config for transfer in) in the code +//! * e.g. if you want to allow process xcm `UniversalOrigin` instruction, +//! you can configure "allowed universal aliases" here and then use it for `xcm_executor::Config`: +//! `type UniversalAliases = AllowedUniversalAliasesOf;` +//! * e.g. if you want to allow process xcm `ReserveAssetDeposited` instruction, +//! you can configure "allowed reserve locations" here and then use it for `xcm_executor::Config`: +//! ```nocompile +//! type IsReserve = IsAllowedReserveOf< +//! Runtime, +//! IsDifferentGlobalConsensusConcreteAsset, +//! >; +//! ``` +//! +//! Transfer in/out are independent so you can configure just to receive or just to send part. +//! All configuration is done by dedicated extrinsics under `AdminOrigin` so for example runtime can allow to change this configuration just by governance. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{transactional, BoundedBTreeSet}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; +use sp_std::boxed::Box; + +pub use pallet::*; +use xcm::prelude::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod impls; +pub mod weights; + +/// The log target of this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-assets-transfer"; + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct BridgeConfig { + /// Contains location, which is able to bridge XCM messages to bridged network + pub bridge_location: MultiLocation, + /// Fee which could be needed to pay in `bridge_location` + /// `MultiAsset` is here from the point of view of `bridge_location`, e.g.: `MultiLocation::parent()` means relay chain token of `bridge_location` + pub bridge_location_fee: Option, + + /// Contains target destination on bridged network. E.g.: MultiLocation of Statemine/t on different consensus + // TODO:check-parameter - lets start with 1..1, maybe later we could extend this with BoundedVec + // TODO: bridged bridge-hub should have router for this + pub allowed_target_location: MultiLocation, + // TODO:check-parameter - can we store Option and then aviod using `Unlimited`? + /// If `None` then `UnpaidExecution` is used, else `Withdraw(target_location_fee)/BuyExecution(target_location_fee, Unlimited)` + /// `MultiAsset` is here from the point of view of `allowed_target_location`, e.g.: `MultiLocation::parent()` means relay chain token of `allowed_target_location` + pub max_target_location_fee: Option, +} + +/// Trait for constructing ping message. +pub trait PingMessageBuilder { + fn try_build( + local_origin: &MultiLocation, + network: &NetworkId, + remote_destination: &MultiLocation, + ) -> Option>; +} + +impl PingMessageBuilder for () { + fn try_build(_: &MultiLocation, _: &NetworkId, _: &MultiLocation) -> Option> { + None + } +} + +/// Builder creates xcm message just with `Trap` instruction. +pub struct UnpaidTrapMessageBuilder(sp_std::marker::PhantomData); +impl> PingMessageBuilder + for UnpaidTrapMessageBuilder +{ + fn try_build(_: &MultiLocation, _: &NetworkId, _: &MultiLocation) -> Option> { + Some(Xcm(sp_std::vec![Trap(TrapCode::get())])) + } +} + +#[frame_support::pallet] +pub mod pallet { + pub use crate::weights::WeightInfo; + + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use xcm_executor::traits::TransactAsset; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Everything we need to run benchmarks. + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + /// Returns proper bridge configuration, supported by the runtime. + /// + /// We expect that the XCM environment (`BridgeXcmSender`) has everything enabled + /// to support transfer to this destination **after** `prepare_asset_transfer` call. + fn bridge_config() -> Option<(NetworkId, BridgeConfig)> { + None + } + + /// Prepare environment for assets transfer and return transfer origin and assets + /// to transfer. After this function is called, we expect `transfer_asset_via_bridge` + /// to succeed, so in proper environment, it should: + /// + /// - deposit enough funds (fee from `bridge_config()` and transferred assets) to the sender account; + /// + /// - ensure that the `BridgeXcmSender` is properly configured for the transfer; + /// + /// - be close to the worst possible scenario - i.e. if some account may need to be created during + /// the assets transfer, it should be created. If there are multiple bridges, the "worst possible" + /// (in terms of performance) bridge must be selected for the transfer. + fn prepare_asset_transfer( + ) -> Option<(RuntimeOrigin, VersionedMultiAssets, VersionedMultiLocation)> { + None + } + + /// Prepare environment for ping transfer and return transfer origin and assets + /// to transfer. After this function is called, we expect `ping_via_bridge` + /// to succeed, so in proper environment, it should: + /// + /// - deposit enough funds (fee from `bridge_config()`) to the sender account; + /// + /// - ensure that the `BridgeXcmSender` is properly configured for the transfer; + /// + /// - be close to the worst possible scenario - i.e. if some account may need to be created during + /// it should be created. If there are multiple bridges, the "worst possible" + /// (in terms of performance) bridge must be selected for the transfer. + fn prepare_ping_transfer() -> Option<(RuntimeOrigin, VersionedMultiLocation)> { + None + } + + fn universal_alias() -> Option<(VersionedMultiLocation, Junction)> { + None + } + + fn reserve_location() -> Option { + None + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Runtime's universal location + type UniversalLocation: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The configurable origin to allow bridges configuration management + type AdminOrigin: EnsureOrigin; + + /// Max allowed universal aliases per one `MultiLocation` + /// (Config for transfer in) + type UniversalAliasesLimit: Get; + /// Max allowed reserve locations + /// (Config for transfer in) + type ReserveLocationsLimit: Get; + + /// How to withdraw and deposit an asset for reserve. + /// (Config for transfer out) + type AssetTransactor: TransactAsset; + /// XCM sender which sends messages to the BridgeHub + /// (Config for transfer out) + type BridgeXcmSender: SendXcm; + /// Required origin for asset transfer. If successful, it resolves to `MultiLocation`. + /// (Config for transfer out) + type TransferAssetOrigin: EnsureOrigin; + /// Max count of assets in one call + /// (Config for transfer out) + type MaxAssetsLimit: Get; + /// Required origin for ping transfer. If successful, it resolves to `MultiLocation`. + /// (Config for transfer out) + type TransferPingOrigin: EnsureOrigin; + /// Configurable ping message, `None` means no message will be transferred. + /// (Config for transfer out) + type PingMessageBuilder: PingMessageBuilder; + + /// Benchmarks helper. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; + } + + /// Details of configured bridges which are allowed for **transfer out**. + /// (Config for transfer out) + #[pallet::storage] + #[pallet::getter(fn allowed_exporters)] + pub(super) type AllowedExporters = + StorageMap<_, Blake2_128Concat, NetworkId, BridgeConfig>; + + /// Holds allowed mappings `MultiLocation->Junction` for `UniversalAliases` + /// E.g: + /// BridgeHubMultiLocation1 -> NetworkId::Kusama + /// BridgeHubMultiLocation1 -> NetworkId::Polkadot + /// (Config for transfer in) + #[pallet::storage] + #[pallet::getter(fn allowed_universal_aliases)] + pub(super) type AllowedUniversalAliases = StorageMap< + _, + Blake2_128Concat, + MultiLocation, + BoundedBTreeSet, + ValueQuery, + >; + + /// Holds allowed mappings `MultiLocation` as trusted reserve locations + /// (Config for transfer in) + #[pallet::storage] + #[pallet::getter(fn allowed_reserve_locations)] + pub(super) type AllowedReserveLocations = + StorageValue<_, BoundedBTreeSet, ValueQuery>; + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + InvalidConfiguration, + UnavailableConfiguration, + ConfigurationAlreadyExists, + InvalidAssets, + MaxAssetsLimitReached, + UnsupportedDestination, + UnsupportedXcmVersion, + InvalidRemoteDestination, + BridgeCallError, + FailedToReserve, + UnsupportedPing, + } + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// Transfer was successfully entered to the system (does not mean already delivered) + TransferInitiated { message_hash: XcmHash, sender_cost: MultiAssets }, + + /// Reserve asset passed + ReserveAssetsDeposited { from: MultiLocation, to: MultiLocation, assets: MultiAssets }, + + /// New bridge configuration was added + BridgeAdded, + /// Bridge configuration was removed + BridgeRemoved, + /// Bridge configuration was updated + BridgeUpdated, + + /// New universal alias was added + UniversalAliasAdded, + /// New universal alias was removed + UniversalAliasRemoved, + + /// New reserve location was added + ReserveLocationAdded, + /// New reserve location was removed + ReserveLocationRemoved, + } + + #[pallet::call] + impl Pallet { + /// Transfer asset via bridge to different global consensus + /// + /// Parameters: + /// + /// * `assets`: + /// * `destination`: Different consensus location, where the assets will be deposited, e.g. Polkadot's Statemint: `2, X2(GlobalConsensus(NetworkId::Polkadot), Parachain(1000))` + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::transfer_asset_via_bridge())] + pub fn transfer_asset_via_bridge( + origin: OriginFor, + assets: Box, + destination: Box, + ) -> DispatchResult { + // Check origin + let origin_location = T::TransferAssetOrigin::ensure_origin(origin)?; + + // Check remote destination + bridge_config + let (_, bridge_config, remote_destination) = + Self::ensure_remote_destination(*destination)?; + + // Check assets (lets leave others checks on `AssetTransactor`) + let assets: MultiAssets = + (*assets).try_into().map_err(|()| Error::::InvalidAssets)?; + ensure!( + assets.len() <= T::MaxAssetsLimit::get() as usize, + Error::::MaxAssetsLimitReached + ); + + // Do this in transaction (explicitly), the rollback should occur in case of any error and no assets will be trapped or lost + Self::do_reserve_and_send_in_transaction( + origin_location, + remote_destination, + assets, + bridge_config, + ) + } + + /// Transfer `ping` via bridge to different global consensus. + /// + /// - can be used for testing purposes that bridge transfer is working and configured for `destination` + /// + /// Parameters: + /// + /// * `destination`: Different consensus location, e.g. Polkadot's Statemint: `2, X2(GlobalConsensus(NetworkId::Polkadot), Parachain(1000))` + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::ping_via_bridge())] + pub fn ping_via_bridge( + origin: OriginFor, + destination: Box, + ) -> DispatchResult { + let origin_location = T::TransferPingOrigin::ensure_origin(origin)?; + + // Check remote destination + bridge_config + let (network, bridge_config, remote_destination) = + Self::ensure_remote_destination(*destination)?; + + // Check reserve account - sovereign account of bridge + let allowed_target_location = bridge_config.allowed_target_location; + + // Prepare `ping` message + let xcm: Xcm<()> = + T::PingMessageBuilder::try_build(&origin_location, &network, &remote_destination) + .ok_or(Error::::UnsupportedPing)?; + + // Initiate bridge transfer + Self::initiate_bridge_transfer(allowed_target_location, xcm).map_err(Into::into) + } + + /// Adds new bridge configuration, which allows transfer to this `bridged_network`. + /// + /// Parameters: + /// + /// * `bridged_network`: Network where we want to allow transfer funds + /// * `bridge_config`: contains location for BridgeHub in our network + fee + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::add_exporter_config())] + pub fn add_exporter_config( + origin: OriginFor, + bridged_network: NetworkId, + bridge_config: Box, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + ensure!( + !AllowedExporters::::contains_key(bridged_network), + Error::::ConfigurationAlreadyExists + ); + let allowed_target_location_network = bridge_config + .allowed_target_location + .interior() + .global_consensus() + .map_err(|_| Error::::InvalidConfiguration)?; + ensure!( + bridged_network == allowed_target_location_network, + Error::::InvalidConfiguration + ); + // bridged consensus must be different + let local_network = T::UniversalLocation::get() + .global_consensus() + .map_err(|_| Error::::InvalidConfiguration)?; + ensure!(bridged_network != local_network, Error::::InvalidConfiguration); + + AllowedExporters::::insert(bridged_network, bridge_config); + Self::deposit_event(Event::BridgeAdded); + Ok(()) + } + + /// Remove bridge configuration for specified `bridged_network`. + /// + /// Parameters: + /// + /// * `bridged_network`: Network where we want to remove + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::remove_exporter_config())] + pub fn remove_exporter_config( + origin: OriginFor, + bridged_network: NetworkId, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + ensure!( + AllowedExporters::::contains_key(bridged_network), + Error::::UnavailableConfiguration + ); + + AllowedExporters::::remove(bridged_network); + Self::deposit_event(Event::BridgeRemoved); + Ok(()) + } + + /// Updates bridge configuration for specified `bridged_network`. + /// + /// Parameters: + /// + /// * `bridged_network`: Network where we want to remove + /// * `fee`: New fee to update + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::update_exporter_config())] + pub fn update_exporter_config( + origin: OriginFor, + bridged_network: NetworkId, + bridge_location_fee: Option>, + target_location_fee: Option>, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + + AllowedExporters::::try_mutate_exists(bridged_network, |maybe_bridge_config| { + let bridge_config = + maybe_bridge_config.as_mut().ok_or(Error::::UnavailableConfiguration)?; + bridge_config.bridge_location_fee = bridge_location_fee + .map(|fee| MultiAsset::try_from(*fee)) + .transpose() + .map_err(|_| Error::::UnsupportedXcmVersion)?; + bridge_config.max_target_location_fee = target_location_fee + .map(|fee| MultiAsset::try_from(*fee)) + .transpose() + .map_err(|_| Error::::UnsupportedXcmVersion)?; + Self::deposit_event(Event::BridgeUpdated); + Ok(()) + }) + } + + /// Add `(MultiLocation, Junction)` mapping to `AllowedUniversalAliases` + /// + /// Parameters: + /// + /// * `location`: key + /// * `junction`: value + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::add_universal_alias())] + pub fn add_universal_alias( + origin: OriginFor, + location: Box, + junction: Junction, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + + let location: MultiLocation = + (*location).try_into().map_err(|_| Error::::UnsupportedXcmVersion)?; + let added = AllowedUniversalAliases::::try_mutate(location, |junctions| { + junctions.try_insert(junction) + }) + .map_err(|_| Error::::InvalidConfiguration)?; + if added { + Self::deposit_event(Event::UniversalAliasAdded); + } + Ok(()) + } + + /// Remove `(MultiLocation, Junction)` mapping from `AllowedUniversalAliases` + /// + /// Parameters: + /// + /// * `location`: key + /// * `junction`: value + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::remove_universal_alias())] + pub fn remove_universal_alias( + origin: OriginFor, + location: Box, + junctions_to_remove: sp_std::prelude::Vec, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + + let location: MultiLocation = + (*location).try_into().map_err(|_| Error::::UnsupportedXcmVersion)?; + let removed = AllowedUniversalAliases::::try_mutate( + location, + |junctions| -> Result> { + let mut removed = false; + for jtr in junctions_to_remove { + removed |= junctions.remove(&jtr); + } + Ok(removed) + }, + )?; + if removed { + Self::deposit_event(Event::UniversalAliasRemoved); + } + Ok(()) + } + + /// Add `MultiLocation` mapping to `AllowedReserveLocations` + /// + /// Parameters: + /// + /// * `location`: as reserve `MultiLocation` + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::add_reserve_location())] + pub fn add_reserve_location( + origin: OriginFor, + location: Box, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + + let location: MultiLocation = + (*location).try_into().map_err(|_| Error::::UnsupportedXcmVersion)?; + let added = AllowedReserveLocations::::try_mutate(|locations| { + locations.try_insert(location) + }) + .map_err(|_| Error::::InvalidConfiguration)?; + if added { + Self::deposit_event(Event::ReserveLocationAdded); + } + Ok(()) + } + + /// Remove `MultiLocation` mapping from `AllowedReserveLocations` + /// + /// Parameters: + /// + /// * `location`: as reserve `MultiLocation` + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::remove_reserve_location())] + pub fn remove_reserve_location( + origin: OriginFor, + locations_to_remove: sp_std::prelude::Vec, + ) -> DispatchResult { + let _ = T::AdminOrigin::ensure_origin(origin)?; + + let removed = + AllowedReserveLocations::::try_mutate(|locations| -> Result> { + let mut removed = false; + for ltr in locations_to_remove { + let ltr: MultiLocation = + ltr.try_into().map_err(|_| Error::::UnsupportedXcmVersion)?; + removed |= locations.remove(<r); + } + Ok(removed) + })?; + if removed { + Self::deposit_event(Event::ReserveLocationRemoved); + } + Ok(()) + } + } + + impl Pallet { + /// Validates destination and check if we support bridging to this remote global consensus + /// + /// Returns: correct remote location, where we should be able to bridge + pub(crate) fn ensure_remote_destination( + remote_destination: VersionedMultiLocation, + ) -> Result<(NetworkId, BridgeConfig, MultiLocation), Error> { + match remote_destination { + VersionedMultiLocation::V3(remote_location) => { + ensure!( + remote_location.parent_count() == 2, + Error::::UnsupportedDestination + ); + let local_network = T::UniversalLocation::get() + .global_consensus() + .map_err(|_| Error::::InvalidConfiguration)?; + let remote_network = remote_location + .interior() + .global_consensus() + .map_err(|_| Error::::UnsupportedDestination)?; + ensure!(local_network != remote_network, Error::::UnsupportedDestination); + match AllowedExporters::::get(remote_network) { + Some(bridge_config) => { + ensure!( + // TODO:check-parameter - verify and prepare test for ETH scenario - https://github.com/paritytech/cumulus/pull/2013#discussion_r1094909290 + remote_location.starts_with(&bridge_config.allowed_target_location), + Error::::UnsupportedDestination + ); + Ok((remote_network, bridge_config, remote_location)) + }, + None => return Err(Error::::UnsupportedDestination), + } + }, + _ => Err(Error::::UnsupportedXcmVersion), + } + } + + #[transactional] + fn do_reserve_and_send_in_transaction( + origin_location: MultiLocation, + remote_destination: MultiLocation, + assets: MultiAssets, + bridge_config: BridgeConfig, + ) -> Result<(), DispatchError> { + // Resolve reserve account as sovereign account of bridge + let reserve_account = bridge_config.bridge_location; + + let allowed_target_location = bridge_config.allowed_target_location; + + // lets try to do a reserve for all assets + let mut reserved_assets = xcm_executor::Assets::new(); + for asset in assets.into_inner() { + // TODO:check-parameter - verify this Joe's text + // Deposit assets into `AccountId` that corresponds to the bridge + // hub. In this way, Statemine acts as a reserve location to the + // bridge, such that it need not trust any consensus system from + // `./Parent/Parent/...`. (It may trust Polkadot, but would + // Polkadot trust Kusama with its DOT?) + + // Move asset to reserve account + T::AssetTransactor::transfer_asset( + &asset, + &origin_location, + &reserve_account, + // We aren't able to track the XCM that initiated the fee deposit, so we create a + // fake message hash here + &XcmContext::with_message_hash([0; 32]), + ) + .and_then(|reserved_asset| { + Self::deposit_event(Event::ReserveAssetsDeposited { + from: origin_location, + to: reserve_account, + assets: reserved_asset.clone().into(), + }); + reserved_assets.subsume_assets(reserved_asset); + Ok(()) + }) + .map_err(|e| { + log::error!( + target: LOG_TARGET, + "AssetTransactor failed to reserve assets from origin_location: {:?} to reserve_account: {:?} for assets: {:?}, error: {:?}", + origin_location, + reserve_account, + asset, + e + ); + Error::::FailedToReserve + })?; + } + + // Prepare `ReserveAssetDeposited` msg to bridge to the other side. + // Reanchor stuff - we need to convert local asset id/MultiLocation to format that could be understood by different consensus and from their point-of-view + reserved_assets.reanchor(&allowed_target_location, T::UniversalLocation::get(), None); + let remote_destination = remote_destination + .reanchored(&allowed_target_location, T::UniversalLocation::get()) + .map_err(|errored_dest| { + log::error!( + target: LOG_TARGET, + "Failed to reanchor remote_destination: {:?} for allowed_target_location: {:?} and universal_location: {:?}", + errored_dest, + allowed_target_location, + T::UniversalLocation::get() + ); + Error::::InvalidRemoteDestination + })?; + + // prepare xcm message (maybe_paid + ReserveAssetDeposited stuff) + let mut xcm_instructions = match bridge_config.max_target_location_fee { + Some(target_location_fee) => sp_std::vec![ + WithdrawAsset(target_location_fee.clone().into()), + BuyExecution { fees: target_location_fee, weight_limit: Unlimited }, + ], + None => + sp_std::vec![UnpaidExecution { check_origin: None, weight_limit: Unlimited }], + }; + xcm_instructions.extend(sp_std::vec![ + ReserveAssetDeposited(reserved_assets.clone().into()), + ClearOrigin, + DepositAsset { + assets: MultiAssetFilter::from(MultiAssets::from(reserved_assets)), + beneficiary: remote_destination + } + ]); + + Self::initiate_bridge_transfer(allowed_target_location, xcm_instructions.into()) + .map_err(Into::into) + } + + fn initiate_bridge_transfer(dest: MultiLocation, xcm: Xcm<()>) -> Result<(), Error> { + log::info!( + target: LOG_TARGET, + "[T::BridgeXcmSender] send to bridge, dest: {:?}, xcm: {:?}", + dest, + xcm, + ); + // call bridge + // TODO: check-parameter - should we handle `sender_cost` somehow ? + let (message_hash, sender_cost) = + send_xcm::(dest, xcm).map_err(|e| { + log::error!( + target: LOG_TARGET, + "[T::BridgeXcmSender] SendError occurred, error: {:?}", + e + ); + Error::::BridgeCallError + })?; + + // just fire event + Self::deposit_event(Event::TransferInitiated { message_hash, sender_cost }); + Ok(()) + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate as bridge_transfer; + use frame_support::traits::{ConstU32, Contains, ContainsPair, Currency, Everything}; + + use crate::impls::{AllowedUniversalAliasesOf, IsAllowedReserveOf}; + use frame_support::{ + assert_noop, assert_ok, dispatch::DispatchError, parameter_types, sp_io, sp_tracing, + }; + use frame_system::EnsureRoot; + use polkadot_parachain::primitives::Sibling; + use sp_runtime::{ + testing::{Header, H256}, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, ModuleError, + }; + use sp_version::RuntimeVersion; + use xcm_builder::{ + AccountId32Aliases, CurrencyAdapter, EnsureXcmOrigin, ExporterFor, IsConcrete, + SiblingParachainConvertsVia, SignedToAccountId32, UnpaidRemoteExporter, + }; + use xcm_executor::traits::Convert; + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + BridgeTransfer: bridge_transfer::{Pallet, Call, Event} = 52, + } + ); + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub Version: RuntimeVersion = RuntimeVersion { + spec_name: sp_version::create_runtime_str!("test"), + impl_name: sp_version::create_runtime_str!("system-test"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_version::create_apis_vec!([]), + transaction_version: 1, + state_version: 1, + }; + } + + pub type AccountId = AccountId32; + + impl frame_system::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockWeights = (); + type Version = Version; + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + parameter_types! { + pub const ExistentialDeposit: u64 = 5; + pub const MaxReserves: u32 = 50; + } + + impl pallet_balances::Config for TestRuntime { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; + } + + parameter_types! { + // UniversalLocation as statemine + pub const RelayNetwork: NetworkId = NetworkId::ByGenesis([9; 32]); + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(1000)); + // Test bridge cfg + pub TestBridgeTable: sp_std::prelude::Vec<(NetworkId, MultiLocation, Option)> = sp_std::vec![ + (NetworkId::Wococo, (Parent, Parachain(1013)).into(), None), + (NetworkId::Polkadot, (Parent, Parachain(1002)).into(), None), + ]; + // Relay chain currency/balance location (e.g. KsmLocation, DotLocation, ..) + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + } + + std::thread_local! { + static ROUTED_MESSAGE: std::cell::RefCell>> = std::cell::RefCell::new(None); + } + + pub struct ThreadLocalXcmRouter; + impl SendXcm for ThreadLocalXcmRouter { + type Ticket = Option>; + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + log::info!( + target: super::LOG_TARGET, + "[ThreadLocalXcmRouter]: destination: {:?}, message: {:?}", + destination, + message + ); + Ok((message.take(), MultiAssets::default())) + } + + fn deliver(ticket: Self::Ticket) -> Result { + match ticket { + Some(msg) => { + ROUTED_MESSAGE.with(|rm| *rm.borrow_mut() = Some(msg)); + Ok([0u8; 32]) + }, + None => Err(SendError::MissingArgument), + } + } + } + + pub struct NotApplicableOrFailOnParachain2222XcmRouter; + impl SendXcm for NotApplicableOrFailOnParachain2222XcmRouter { + type Ticket = Option>; + + fn validate( + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + log::info!( + target: super::LOG_TARGET, + "[NotApplicableOrFailOnParachain2222XcmRouter]: destination: {:?}, message: {:?}", + destination, + message + ); + if matches!( + destination, + Some(MultiLocation { interior: X1(Parachain(2222)), parents: 1 }) + ) { + Err(SendError::Transport("Simulate what ever error")) + } else { + Err(SendError::NotApplicable) + } + } + + fn deliver(ticket: Self::Ticket) -> Result { + unimplemented!("We should not come here, ticket: {:?}", ticket) + } + } + + pub type XcmRouter = (NotApplicableOrFailOnParachain2222XcmRouter, ThreadLocalXcmRouter); + + /// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different GlobalConsensus + pub type TestBridgeXcmSender = + UnpaidRemoteExporter; + + /// No local origins on this chain are allowed to dispatch XCM sends/executions. + pub type LocalOriginToLocation = SignedToAccountId32; + + pub type LocationToAccountId = ( + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, + ); + + /// 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`. + (), + >; + + /// Bridge configuration we use in our tests. + fn test_bridge_config() -> (NetworkId, BridgeConfig) { + ( + Wococo, + BridgeConfig { + bridge_location: (Parent, Parachain(1013)).into(), + bridge_location_fee: None, + allowed_target_location: MultiLocation::new( + 2, + X2(GlobalConsensus(Wococo), Parachain(1000)), + ), + max_target_location_fee: None, + }, + ) + } + + /// Benchmarks helper. + #[cfg(feature = "runtime-benchmarks")] + pub struct TestBenchmarkHelper; + + #[cfg(feature = "runtime-benchmarks")] + impl BenchmarkHelper for TestBenchmarkHelper { + fn bridge_config() -> (NetworkId, BridgeConfig) { + test_bridge_config() + } + + fn prepare_asset_transfer() -> (RuntimeOrigin, VersionedMultiAssets, VersionedMultiLocation) + { + let assets_count = MaxAssetsLimit::get(); + + // sender account must have enough funds + let sender_account = account(1); + let total_deposit = ExistentialDeposit::get() * (1 + assets_count as u64); + let _ = Balances::deposit_creating(&sender_account, total_deposit); + + // finally - prepare assets and destination + let assets = VersionedMultiAssets::V3( + std::iter::repeat(MultiAsset { + fun: Fungible(ExistentialDeposit::get().into()), + id: Concrete(RelayLocation::get()), + }) + .take(assets_count as usize) + .collect::>() + .into(), + ); + let destination = VersionedMultiLocation::V3(MultiLocation::new( + 2, + X3(GlobalConsensus(Wococo), Parachain(1000), consensus_account(Wococo, 2)), + )); + + (RuntimeOrigin::signed(sender_account), assets, destination) + } + + fn prepare_ping_transfer() { + unimplemented!("Not implemented here - not needed"); + } + } + + parameter_types! { + pub const TrapCode: u64 = 12345; + pub const MaxAssetsLimit: u8 = 1; + } + + impl Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type UniversalLocation = UniversalLocation; + type WeightInfo = (); + type AdminOrigin = EnsureRoot; + type UniversalAliasesLimit = ConstU32<2>; + type ReserveLocationsLimit = ConstU32<2>; + type AssetTransactor = CurrencyTransactor; + type BridgeXcmSender = TestBridgeXcmSender; + type TransferAssetOrigin = EnsureXcmOrigin; + type MaxAssetsLimit = MaxAssetsLimit; + type TransferPingOrigin = EnsureXcmOrigin; + type PingMessageBuilder = UnpaidTrapMessageBuilder; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = TestBenchmarkHelper; + } + + pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + // with 0 block_number events dont work + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + frame_system::Pallet::::set_block_number(1u32.into()); + }); + + ext + } + + fn account(account: u8) -> AccountId32 { + AccountId32::new([account; 32]) + } + + fn consensus_account(network: NetworkId, account: u8) -> Junction { + xcm::prelude::AccountId32 { + network: Some(network), + id: AccountId32::new([account; 32]).into(), + } + } + + #[test] + fn test_ensure_remote_destination() { + new_test_ext().execute_with(|| { + // insert bridge config + let bridge_network = Wococo; + let bridge_config = test_bridge_config().1; + assert_ok!(BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + bridge_network, + Box::new(bridge_config.clone()), + )); + + // v2 not supported + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V2( + xcm::v2::MultiLocation::default() + )), + Err(Error::::UnsupportedXcmVersion) + ); + + // v3 - "parent: 0" wrong + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V3( + MultiLocation::new(0, X2(GlobalConsensus(Wococo), Parachain(1000))) + )), + Err(Error::::UnsupportedDestination) + ); + // v3 - "parent: 1" wrong + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V3( + MultiLocation::new(1, X2(GlobalConsensus(Wococo), Parachain(1000))) + )), + Err(Error::::UnsupportedDestination) + ); + + // v3 - Rococo is not supported + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V3( + MultiLocation::new(2, X2(GlobalConsensus(Rococo), Parachain(1000))) + )), + Err(Error::::UnsupportedDestination) + ); + + // v3 - remote_destination is not allowed + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V3( + MultiLocation::new(2, X2(GlobalConsensus(Wococo), Parachain(1234))) + )), + Err(Error::::UnsupportedDestination) + ); + + // v3 - ok (allowed) + assert_eq!( + BridgeTransfer::ensure_remote_destination(VersionedMultiLocation::V3( + MultiLocation::new(2, X2(GlobalConsensus(Wococo), Parachain(1000))) + )), + Ok(( + bridge_network, + bridge_config, + MultiLocation::new(2, X2(GlobalConsensus(Wococo), Parachain(1000))) + )) + ); + }) + } + + #[test] + fn test_transfer_asset_via_bridge_for_currency_works() { + new_test_ext().execute_with(|| { + // initialize some Balances for user_account + let user_account = account(1); + let user_account_init_balance = 1000_u64; + let _ = Balances::deposit_creating(&user_account, user_account_init_balance); + let user_free_balance = Balances::free_balance(&user_account); + let balance_to_transfer = 15_u64; + assert!((user_free_balance - balance_to_transfer) >= ExistentialDeposit::get()); + // because, sovereign account needs to have ED otherwise reserve fails + assert!(balance_to_transfer >= ExistentialDeposit::get()); + + // insert bridge config + let bridged_network = Wococo; + assert_ok!(BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + bridged_network, + Box::new(test_bridge_config().1), + )); + let bridge_location = AllowedExporters::::get(bridged_network) + .expect("stored BridgeConfig for bridged_network") + .bridge_location; + + // checks before + assert!(ROUTED_MESSAGE.with(|r| r.borrow().is_none())); + assert_eq!(Balances::free_balance(&user_account), user_account_init_balance); + let bridge_location_as_sovereign_account = + LocationToAccountId::convert_ref(bridge_location) + .expect("converted bridge location as accountId"); + assert_eq!(Balances::free_balance(&bridge_location_as_sovereign_account), 0); + + // trigger transfer_asset_via_bridge - should trigger new ROUTED_MESSAGE + let asset = MultiAsset { + fun: Fungible(balance_to_transfer.into()), + id: Concrete(RelayLocation::get()), + }; + let assets = Box::new(VersionedMultiAssets::from(MultiAssets::from(asset))); + + // destination is account from different consensus + let destination = Box::new(VersionedMultiLocation::from(MultiLocation::new( + 2, + X3(GlobalConsensus(Wococo), Parachain(1000), consensus_account(Wococo, 2)), + ))); + + // trigger asset transfer + assert_ok!(BridgeTransfer::transfer_asset_via_bridge( + RuntimeOrigin::signed(account(1)), + assets, + destination, + )); + + // check user account decressed + assert_eq!( + Balances::free_balance(&user_account), + user_account_init_balance - balance_to_transfer + ); + // check reserve account increased + assert_eq!(Balances::free_balance(&bridge_location_as_sovereign_account), 15); + + // check events + let events = System::events(); + assert!(!events.is_empty()); + + // check reserve asset deposited event + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::BridgeTransfer(Event::ReserveAssetsDeposited { .. }) + ))); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::BridgeTransfer(Event::TransferInitiated { .. }) + ))); + + // check fired XCM ExportMessage to bridge-hub + let fired_xcm = + ROUTED_MESSAGE.with(|r| r.take().expect("xcm::ExportMessage should be here")); + + if let Some(ExportMessage { xcm, .. }) = fired_xcm.0.iter().find(|instr| { + matches!( + instr, + ExportMessage { network: Wococo, destination: X1(Parachain(1000)), .. } + ) + }) { + assert!(xcm.0.iter().any(|instr| matches!(instr, UnpaidExecution { .. }))); + assert!(xcm.0.iter().any(|instr| matches!(instr, ReserveAssetDeposited(..)))); + assert!(xcm.0.iter().any(|instr| matches!(instr, ClearOrigin))); + assert!(xcm.0.iter().any(|instr| matches!(instr, DepositAsset { .. }))); + } else { + assert!(false, "Does not contains [`ExportMessage`], fired_xcm: {:?}", fired_xcm); + } + }); + } + + #[test] + fn test_transfer_asset_via_bridge_in_case_of_error_transactional_works() { + new_test_ext().execute_with(|| { + // initialize some Balances for user_account + let user_account = account(1); + let user_account_init_balance = 1000_u64; + let _ = Balances::deposit_creating(&user_account, user_account_init_balance); + let user_free_balance = Balances::free_balance(&user_account); + let balance_to_transfer = 15_u64; + assert!((user_free_balance - balance_to_transfer) >= ExistentialDeposit::get()); + // because, sovereign account needs to have ED otherwise reserve fails + assert!(balance_to_transfer >= ExistentialDeposit::get()); + + // insert bridge config (with unroutable bridge_location - 2222) + let bridged_network = Wococo; + assert_ok!(BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + bridged_network, + Box::new(BridgeConfig { + bridge_location: MultiLocation::new(1, Parachain(2222)).into(), + bridge_location_fee: None, + allowed_target_location: MultiLocation::new( + 2, + X2(GlobalConsensus(Wococo), Parachain(1000)), + ), + max_target_location_fee: None, + }), + )); + + let bridge_location = AllowedExporters::::get(bridged_network) + .expect("stored BridgeConfig for bridged_network") + .bridge_location; + + // checks before + assert!(ROUTED_MESSAGE.with(|r| r.borrow().is_none())); + let user_balance_before = Balances::free_balance(&user_account); + assert_eq!(user_balance_before, user_account_init_balance); + let bridge_location_as_sovereign_account = + LocationToAccountId::convert_ref(bridge_location) + .expect("converted bridge location as accountId"); + let reserve_account_before = + Balances::free_balance(&bridge_location_as_sovereign_account); + assert_eq!(reserve_account_before, 0); + + // trigger transfer_asset_via_bridge - should trigger new ROUTED_MESSAGE + let asset = MultiAsset { + fun: Fungible(balance_to_transfer.into()), + id: Concrete(RelayLocation::get()), + }; + let assets = Box::new(VersionedMultiAssets::from(MultiAssets::from(asset))); + + // destination is account from different consensus + let destination = Box::new(VersionedMultiLocation::from(MultiLocation::new( + 2, + X3(GlobalConsensus(Wococo), Parachain(1000), consensus_account(Wococo, 2)), + ))); + + // reset events + System::reset_events(); + + // trigger asset transfer + assert_noop!( + BridgeTransfer::transfer_asset_via_bridge( + RuntimeOrigin::signed(account(1)), + assets, + destination + ), + DispatchError::Module(ModuleError { + index: 52, + error: [8, 0, 0, 0], + message: Some("BridgeCallError") + }) + ); + + // checks after + // balances are untouched + assert_eq!(Balances::free_balance(&user_account), user_balance_before); + assert_eq!( + Balances::free_balance(&bridge_location_as_sovereign_account), + reserve_account_before + ); + // no xcm messages fired + assert!(ROUTED_MESSAGE.with(|r| r.borrow().is_none())); + // check events (no events because of rollback) + assert!(System::events().is_empty()); + }); + } + + #[test] + fn test_ping_via_bridge_works() { + new_test_ext().execute_with(|| { + // insert bridge config + let bridged_network = Wococo; + assert_ok!(BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + bridged_network, + Box::new(test_bridge_config().1), + )); + + // checks before + assert!(ROUTED_MESSAGE.with(|r| r.borrow().is_none())); + + // trigger ping_via_bridge - should trigger new ROUTED_MESSAGE + // destination is account from different consensus + let destination = Box::new(VersionedMultiLocation::V3(MultiLocation::new( + 2, + X3(GlobalConsensus(Wococo), Parachain(1000), consensus_account(Wococo, 2)), + ))); + + // trigger asset transfer + assert_ok!(BridgeTransfer::ping_via_bridge( + RuntimeOrigin::signed(account(1)), + destination, + )); + + // check events + let events = System::events(); + assert!(!events.is_empty()); + + // check TransferInitiated + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::BridgeTransfer(Event::TransferInitiated { .. }) + ))); + + // check fired XCM ExportMessage to bridge-hub + let fired_xcm = + ROUTED_MESSAGE.with(|r| r.take().expect("xcm::ExportMessage should be here")); + + if let Some(ExportMessage { xcm, .. }) = fired_xcm.0.iter().find(|instr| { + matches!( + instr, + ExportMessage { network: Wococo, destination: X1(Parachain(1000)), .. } + ) + }) { + assert!(xcm.0.iter().any(|instr| instr.eq(&Trap(TrapCode::get())))); + } else { + assert!(false, "Does not contains [`ExportMessage`], fired_xcm: {:?}", fired_xcm); + } + }); + } + + #[test] + fn allowed_exporters_management_works() { + let bridged_network = Rococo; + let bridged_config = Box::new(BridgeConfig { + bridge_location: (Parent, Parachain(1013)).into(), + bridge_location_fee: None, + allowed_target_location: MultiLocation::new( + 2, + X2(GlobalConsensus(bridged_network), Parachain(1000)), + ), + max_target_location_fee: None, + }); + let dummy_xcm = Xcm(vec![]); + let dummy_remote_interior_multilocation = X1(Parachain(1234)); + + { + let mut asset = xcm_executor::Assets::from(MultiAsset { + id: Concrete(MultiLocation::parent()), + fun: Fungible(1_000), + }); + println!("before: {:?}", asset); + asset.reanchor(&bridged_config.allowed_target_location, UniversalLocation::get(), None); + println!("after: {:?}", asset); + } + { + let mut asset = xcm_executor::Assets::from(MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: X1(Parachain(3000)) }), + fun: Fungible(1_000), + }); + println!("before: {:?}", asset); + asset.reanchor(&bridged_config.allowed_target_location, UniversalLocation::get(), None); + println!("after: {:?}", asset); + } + + new_test_ext().execute_with(|| { + assert_eq!(AllowedExporters::::iter().count(), 0); + + // should fail - just root is allowed + assert_noop!( + BridgeTransfer::add_exporter_config( + RuntimeOrigin::signed(account(1)), + bridged_network, + bridged_config.clone(), + ), + DispatchError::BadOrigin + ); + + // should fail - bridged_network should match allowed_target_location + assert_noop!( + BridgeTransfer::add_exporter_config(RuntimeOrigin::root(), bridged_network, { + let remote_network = Westend; + assert_ne!(bridged_network, remote_network); + Box::new(test_bridge_config().1) + }), + DispatchError::Module(ModuleError { + index: 52, + error: [0, 0, 0, 0], + message: Some("InvalidConfiguration") + }) + ); + // should fail - bridged_network must be different global consensus than our `UniversalLocation` + assert_noop!( + BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + UniversalLocation::get().global_consensus().expect("any `NetworkId`"), + bridged_config.clone() + ), + DispatchError::Module(ModuleError { + index: 52, + error: [0, 0, 0, 0], + message: Some("InvalidConfiguration") + }) + ); + assert_eq!(AllowedExporters::::iter().count(), 0); + assert_eq!( + BridgeTransfer::exporter_for( + &bridged_network, + &dummy_remote_interior_multilocation, + &dummy_xcm + ), + None + ); + + // add with root + assert_ok!(BridgeTransfer::add_exporter_config( + RuntimeOrigin::root(), + bridged_network, + bridged_config.clone(), + )); + assert_eq!(AllowedExporters::::iter().count(), 1); + assert_eq!( + AllowedExporters::::get(bridged_network), + Some(*bridged_config.clone()) + ); + assert_eq!(AllowedExporters::::get(Wococo), None); + assert_eq!( + BridgeTransfer::exporter_for( + &bridged_network, + &dummy_remote_interior_multilocation, + &dummy_xcm + ), + Some((bridged_config.bridge_location, bridged_config.bridge_location_fee)) + ); + assert_eq!( + BridgeTransfer::exporter_for( + &Wococo, + &dummy_remote_interior_multilocation, + &dummy_xcm + ), + None + ); + + // update fee + // remove + assert_ok!(BridgeTransfer::update_exporter_config( + RuntimeOrigin::root(), + bridged_network, + Some(VersionedMultiAsset::V3((Parent, 200u128).into()).into()), + Some(VersionedMultiAsset::V3((Parent, 300u128).into()).into()), + )); + assert_eq!(AllowedExporters::::iter().count(), 1); + assert_eq!( + AllowedExporters::::get(bridged_network), + Some(BridgeConfig { + bridge_location: bridged_config.bridge_location.clone(), + bridge_location_fee: Some((Parent, 200u128).into()), + allowed_target_location: bridged_config.allowed_target_location.clone(), + max_target_location_fee: Some((Parent, 300u128).into()), + }) + ); + assert_eq!( + BridgeTransfer::exporter_for( + &bridged_network, + &dummy_remote_interior_multilocation, + &dummy_xcm + ), + Some((bridged_config.bridge_location, Some((Parent, 200u128).into()))) + ); + + // remove + assert_ok!(BridgeTransfer::remove_exporter_config( + RuntimeOrigin::root(), + bridged_network, + )); + assert_eq!(AllowedExporters::::get(bridged_network), None); + assert_eq!(AllowedExporters::::iter().count(), 0); + }) + } + + #[test] + fn allowed_universal_aliases_management_works() { + new_test_ext().execute_with(|| { + assert_eq!(AllowedUniversalAliases::::iter().count(), 0); + + let location1 = MultiLocation::new(1, X1(Parachain(1014))); + let junction1 = GlobalConsensus(ByGenesis([1; 32])); + let junction2 = GlobalConsensus(ByGenesis([2; 32])); + + // should fail - just root is allowed + assert_noop!( + BridgeTransfer::add_universal_alias( + RuntimeOrigin::signed(account(1)), + Box::new(VersionedMultiLocation::V3(location1.clone())), + junction1.clone(), + ), + DispatchError::BadOrigin + ); + assert_eq!(AllowedUniversalAliases::::iter().count(), 0); + assert!(!AllowedUniversalAliasesOf::::contains(&(location1, junction1))); + assert!(!AllowedUniversalAliasesOf::::contains(&(location1, junction2))); + + // add ok + assert_ok!(BridgeTransfer::add_universal_alias( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location1.clone())), + junction1.clone(), + )); + assert_ok!(BridgeTransfer::add_universal_alias( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location1.clone())), + junction2.clone(), + )); + assert!(AllowedUniversalAliasesOf::::contains(&(location1, junction1))); + assert!(AllowedUniversalAliasesOf::::contains(&(location1, junction2))); + + // remove ok + assert_ok!(BridgeTransfer::remove_universal_alias( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location1.clone())), + vec![junction1.clone()], + )); + assert!(!AllowedUniversalAliasesOf::::contains(&(location1, junction1))); + assert!(AllowedUniversalAliasesOf::::contains(&(location1, junction2))); + + assert_ok!(BridgeTransfer::remove_universal_alias( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location1.clone())), + vec![junction2.clone()], + )); + assert!(!AllowedUniversalAliasesOf::::contains(&(location1, junction1))); + assert!(!AllowedUniversalAliasesOf::::contains(&(location1, junction2))); + }) + } + + #[test] + fn allowed_reserve_locations_management_works() { + new_test_ext().execute_with(|| { + assert!(AllowedReserveLocations::::get().is_empty()); + + let location1 = MultiLocation::new(1, X1(Parachain(1014))); + let location2 = + MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([1; 32])), Parachain(1014))); + let asset: MultiAsset = (Parent, 200u128).into(); + + // should fail - just root is allowed + assert_noop!( + BridgeTransfer::add_reserve_location( + RuntimeOrigin::signed(account(1)), + Box::new(VersionedMultiLocation::V3(location1.clone())) + ), + DispatchError::BadOrigin + ); + assert_eq!(AllowedReserveLocations::::get().len(), 0); + assert!(!IsAllowedReserveOf::::contains(&asset, &location1)); + assert!(!IsAllowedReserveOf::::contains(&asset, &location2)); + + // add ok + assert_ok!(BridgeTransfer::add_reserve_location( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location1.clone())) + )); + assert_ok!(BridgeTransfer::add_reserve_location( + RuntimeOrigin::root(), + Box::new(VersionedMultiLocation::V3(location2.clone())) + )); + assert_eq!(AllowedReserveLocations::::get().len(), 2); + assert!(IsAllowedReserveOf::::contains(&asset, &location1)); + assert!(IsAllowedReserveOf::::contains(&asset, &location2)); + + // remove ok + assert_ok!(BridgeTransfer::remove_reserve_location( + RuntimeOrigin::root(), + vec![VersionedMultiLocation::V3(location1.clone())], + )); + assert_eq!(AllowedReserveLocations::::get().len(), 1); + assert!(!IsAllowedReserveOf::::contains(&asset, &location1)); + assert!(IsAllowedReserveOf::::contains(&asset, &location2)); + + assert_ok!(BridgeTransfer::remove_reserve_location( + RuntimeOrigin::root(), + vec![VersionedMultiLocation::V3(location2.clone())], + )); + assert!(AllowedReserveLocations::::get().is_empty()); + assert!(!IsAllowedReserveOf::::contains(&asset, &location1)); + assert!(!IsAllowedReserveOf::::contains(&asset, &location2)); + }) + } +} diff --git a/parachains/pallets/bridge-transfer/src/weights.rs b/parachains/pallets/bridge-transfer/src/weights.rs new file mode 100644 index 00000000000..be1eb001980 --- /dev/null +++ b/parachains/pallets/bridge-transfer/src/weights.rs @@ -0,0 +1,87 @@ +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Weights trait for the `pallet_bridge_assets_transfer` pallet. + +#![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_bridge_assets_transfer. +pub trait WeightInfo { + /// Weight of the `transfer_asset_via_bridge` call. + fn transfer_asset_via_bridge() -> Weight; + /// Weight of the `ping_via_bridge` call. + fn ping_via_bridge() -> Weight; + + /// Weight of the `add_exporter_config` call. + fn add_exporter_config() -> Weight; + /// Weight of the `remove_exporter_config` call. + fn remove_exporter_config() -> Weight; + /// Weight of the `update_exporter_config` call. + fn update_exporter_config() -> Weight; + + /// Weight of the `add_universal_alias` call. + fn add_universal_alias() -> Weight; + /// Weight of the `remove_universal_alias` call. + fn remove_universal_alias() -> Weight; + + /// Weight of the `add_reserve_location` call. + fn add_reserve_location() -> Weight; + /// Weight of the `remove_reserve_location` call. + fn remove_reserve_location() -> Weight; +} + +// Zero weights to use in tests +impl WeightInfo for () { + fn transfer_asset_via_bridge() -> Weight { + Weight::zero() + } + + fn ping_via_bridge() -> Weight { + Weight::zero() + } + + fn add_exporter_config() -> Weight { + Weight::zero() + } + + fn remove_exporter_config() -> Weight { + Weight::zero() + } + + fn update_exporter_config() -> Weight { + Weight::zero() + } + + fn add_universal_alias() -> Weight { + Weight::zero() + } + + fn remove_universal_alias() -> Weight { + Weight::zero() + } + + fn add_reserve_location() -> Weight { + Weight::zero() + } + + fn remove_reserve_location() -> Weight { + Weight::zero() + } +} diff --git a/parachains/runtimes/assets/common/src/lib.rs b/parachains/runtimes/assets/common/src/lib.rs index 8a321ad97aa..6e29623d7e4 100644 --- a/parachains/runtimes/assets/common/src/lib.rs +++ b/parachains/runtimes/assets/common/src/lib.rs @@ -17,6 +17,7 @@ pub mod foreign_creators; pub mod fungible_conversion; +pub mod location_conversion; pub mod matching; pub mod runtime_api; diff --git a/parachains/runtimes/assets/common/src/location_conversion.rs b/parachains/runtimes/assets/common/src/location_conversion.rs new file mode 100644 index 00000000000..50e06f27b20 --- /dev/null +++ b/parachains/runtimes/assets/common/src/location_conversion.rs @@ -0,0 +1,152 @@ +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO:check-parameter - is it worth to move it to the [`xcm-builder -> location_conversion.rs`]? + +use codec::Encode; +use frame_support::sp_io::hashing::blake2_256; +use sp_std::{borrow::Borrow, marker::PhantomData}; +use xcm::prelude::*; +use xcm_executor::traits::Convert; + +/// Tries to convert **foreign** global consensus parachain to accountId. +/// +/// **foreign** means `parents > 1` +/// +/// (E.g.: can be used for sovereign account conversion) +pub struct GlobalConsensusParachainConvert(PhantomData); + +impl + Clone> Convert + for GlobalConsensusParachainConvert +{ + fn convert_ref(location: impl Borrow) -> Result { + match location.borrow() { + MultiLocation { + parents, + interior: X2(GlobalConsensus(network), Parachain(para_id)), + } if parents > &1_u8 => + Ok(AccountId::from(GlobalConsensusParachainConvert::::from_params( + network, para_id, *parents, + ))), + _ => Err(()), + } + } + + fn reverse_ref(_: impl Borrow) -> Result { + // if this will be needed, we could implement some kind of guessing, if we have configuration for supported foreign networkId+paraId + Err(()) + } +} + +impl GlobalConsensusParachainConvert { + fn from_params(network: &NetworkId, para_id: &u32, parents: u8) -> [u8; 32] { + (network, para_id, parents).using_encoded(blake2_256) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn global_consensus_parachain_convert_works() { + let test_data = vec![ + ( + MultiLocation::new(0, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), + false, + ), + ( + MultiLocation::new(1, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), + false, + ), + ( + MultiLocation::new( + 2, + X3( + GlobalConsensus(ByGenesis([0; 32])), + Parachain(1000), + AccountId32 { network: None, id: [1; 32].into() }, + ), + ), + false, + ), + (MultiLocation::new(2, X1(GlobalConsensus(ByGenesis([0; 32])))), false), + (MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), true), + (MultiLocation::new(3, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), true), + (MultiLocation::new(4, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), true), + ( + MultiLocation::new(10, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), + true, + ), + ]; + + for (location, expected_result) in test_data { + let result = GlobalConsensusParachainConvert::<[u8; 32]>::convert_ref(&location); + match result { + Ok(account) => { + assert_eq!( + true, expected_result, + "expected_result: {}, but conversion passed: {:?}, location: {:?}", + expected_result, account, location + ); + match &location { + MultiLocation { parents, interior: X2(GlobalConsensus(network), Parachain(para_id)) } => + assert_eq!( + account, + GlobalConsensusParachainConvert::<[u8; 32]>::from_params(network, para_id, *parents), + "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location + ), + _ => assert_eq!( + true, + expected_result, + "expected_result: {}, conversion passed: {:?}, but MultiLocation does not match expected pattern, location: {:?}", expected_result, account, location + ) + } + }, + Err(_) => { + assert_eq!( + false, expected_result, + "expected_result: {} - but conversion failed, location: {:?}", + expected_result, location + ); + }, + } + } + + // all success + let res_2_1000 = GlobalConsensusParachainConvert::<[u8; 32]>::convert_ref( + MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), + ) + .expect("conversion is ok"); + let res_2_1001 = GlobalConsensusParachainConvert::<[u8; 32]>::convert_ref( + MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1001))), + ) + .expect("conversion is ok"); + let res_3_1000 = GlobalConsensusParachainConvert::<[u8; 32]>::convert_ref( + MultiLocation::new(3, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), + ) + .expect("conversion is ok"); + let res_3_1001 = GlobalConsensusParachainConvert::<[u8; 32]>::convert_ref( + MultiLocation::new(3, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1001))), + ) + .expect("conversion is ok"); + assert_ne!(res_2_1000, res_2_1001); + assert_ne!(res_2_1000, res_3_1000); + assert_ne!(res_2_1000, res_3_1001); + assert_ne!(res_2_1001, res_3_1000); + assert_ne!(res_2_1001, res_3_1001); + assert_ne!(res_3_1000, res_3_1001); + } +} diff --git a/parachains/runtimes/assets/common/src/matching.rs b/parachains/runtimes/assets/common/src/matching.rs index a5e030412b9..00793b65e67 100644 --- a/parachains/runtimes/assets/common/src/matching.rs +++ b/parachains/runtimes/assets/common/src/matching.rs @@ -89,3 +89,25 @@ impl> ContainsPair } } } + +/// Accepts an asset if it is from different global consensus than self plus `parents > 1` +pub struct IsDifferentGlobalConsensusConcreteAsset( + sp_std::marker::PhantomData, +); +impl> ContainsPair + for IsDifferentGlobalConsensusConcreteAsset +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "IsDifferentGlobalConsensusConcreteAsset asset: {:?}, origin: {:?}", asset, origin); + match asset { + MultiAsset { id: Concrete(asset_location), .. } if asset_location.parents > 1 => + match asset_location.first_interior() { + Some(GlobalConsensus(asset_consensus)) + if asset_consensus != &SelfGlobalConsensus::get() => + true, + _ => false, + }, + _ => false, + } + } +} diff --git a/parachains/runtimes/assets/statemine/Cargo.toml b/parachains/runtimes/assets/statemine/Cargo.toml index c3231e23958..f4682c2f0c2 100644 --- a/parachains/runtimes/assets/statemine/Cargo.toml +++ b/parachains/runtimes/assets/statemine/Cargo.toml @@ -74,9 +74,11 @@ pallet-collator-selection = { path = "../../../../pallets/collator-selection", d parachain-info = { path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } assets-common = { path = "../common", default-features = false } +pallet-bridge-transfer = { path = "../../../pallets/bridge-transfer", default-features = false } [dev-dependencies] asset-test-utils = { path = "../test-utils"} +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } [build-dependencies] substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "master", optional = true } @@ -107,12 +109,14 @@ runtime-benchmarks = [ "pallet-xcm/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", "assets-common/runtime-benchmarks", + "pallet-bridge-transfer/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", @@ -140,6 +144,7 @@ try-runtime = [ "pallet-xcm/try-runtime", "parachain-info/try-runtime", "pallet-state-trie-migration/try-runtime", + "pallet-bridge-transfer/try-runtime", ] std = [ "codec/std", @@ -196,5 +201,6 @@ std = [ "parachain-info/std", "parachains-common/std", "assets-common/std", + "pallet-bridge-transfer/std", "substrate-wasm-builder", ] diff --git a/parachains/runtimes/assets/statemine/src/lib.rs b/parachains/runtimes/assets/statemine/src/lib.rs index 961a9300a56..05c3d7780f0 100644 --- a/parachains/runtimes/assets/statemine/src/lib.rs +++ b/parachains/runtimes/assets/statemine/src/lib.rs @@ -69,8 +69,9 @@ use parachains_common::{ NORMAL_DISPATCH_RATIO, SLOT_DURATION, }; use xcm_config::{ - FellowshipLocation, ForeignAssetsConvertedConcreteId, GovernanceLocation, KsmLocation, - TrustBackedAssetsConvertedConcreteId, XcmConfig, + AssetTransactors, BridgeXcmSender, FellowshipLocation, ForeignAssetsConvertedConcreteId, + GovernanceLocation, KsmLocation, LocalOriginToLocation, TrustBackedAssetsConvertedConcreteId, + UniversalLocation, XcmConfig, }; #[cfg(any(feature = "std", test))] @@ -80,6 +81,7 @@ pub use sp_runtime::BuildStorage; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use xcm::latest::BodyId; +use xcm_builder::EnsureXcmOrigin; use xcm_executor::XcmExecutor; use crate::xcm_config::ForeignCreatorsSovereignAccountOf; @@ -693,6 +695,25 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } +impl pallet_bridge_transfer::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type UniversalLocation = UniversalLocation; + type WeightInfo = weights::pallet_bridge_transfer::WeightInfo; + type AdminOrigin = AssetsForceOrigin; + // no transfer allowed in (now) + type UniversalAliasesLimit = ConstU32<0>; + // no transfer allowed in (now) + type ReserveLocationsLimit = ConstU32<0>; + type AssetTransactor = AssetTransactors; + type BridgeXcmSender = BridgeXcmSender; + type TransferAssetOrigin = EnsureXcmOrigin; + type MaxAssetsLimit = ConstU8<1>; + type TransferPingOrigin = EnsureXcmOrigin; + type PingMessageBuilder = pallet_bridge_transfer::UnpaidTrapMessageBuilder>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = xcm_config::BridgeTransferBenchmarksHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where @@ -737,6 +758,7 @@ construct_runtime!( Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, + BridgeTransfer: pallet_bridge_transfer::{Pallet, Call, Storage, Event} = 54, #[cfg(feature = "state-trie-version-1")] StateTrieMigration: pallet_state_trie_migration = 70, @@ -800,6 +822,7 @@ mod benches { [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_bridge_transfer, BridgeTransfer] // XCM [pallet_xcm, PolkadotXcm] // NOTE: Make sure you point to the individual modules below. diff --git a/parachains/runtimes/assets/statemine/src/weights/mod.rs b/parachains/runtimes/assets/statemine/src/weights/mod.rs index 92af360ced1..518bd88bc2e 100644 --- a/parachains/runtimes/assets/statemine/src/weights/mod.rs +++ b/parachains/runtimes/assets/statemine/src/weights/mod.rs @@ -4,6 +4,7 @@ pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_assets; pub mod pallet_balances; +pub mod pallet_bridge_transfer; pub mod pallet_collator_selection; pub mod pallet_multisig; pub mod pallet_nfts; diff --git a/parachains/runtimes/assets/statemine/src/weights/pallet_bridge_transfer.rs b/parachains/runtimes/assets/statemine/src/weights/pallet_bridge_transfer.rs new file mode 100644 index 00000000000..47a63b0eb36 --- /dev/null +++ b/parachains/runtimes/assets/statemine/src/weights/pallet_bridge_transfer.rs @@ -0,0 +1,175 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `pallet_bridge_transfer` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("statemine-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json +// --pallet=pallet_bridge_transfer +// --chain=statemine-dev +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/statemine/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_bridge_transfer`. +pub struct WeightInfo(PhantomData); +impl pallet_bridge_transfer::WeightInfo for WeightInfo { + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: BridgeTransfer AllowedExporters (r:1 w:0) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// 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) + /// Storage: ParachainSystem RelevantMessagingState (r:1 w:0) + /// Proof Skipped: ParachainSystem RelevantMessagingState (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: XcmpQueue OutboundXcmpStatus (r:1 w:1) + /// Proof Skipped: XcmpQueue OutboundXcmpStatus (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: XcmpQueue OutboundXcmpMessages (r:0 w:1) + /// Proof Skipped: XcmpQueue OutboundXcmpMessages (max_values: None, max_size: None, mode: Measured) + fn transfer_asset_via_bridge() -> Weight { + // Proof Size summary in bytes: + // Measured: `477` + // Estimated: `25954` + // Minimum execution time: 126_493_000 picoseconds. + Weight::from_parts(127_684_000, 0) + .saturating_add(Weight::from_parts(0, 25954)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: BridgeTransfer AllowedExporters (r:1 w:0) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + /// 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) + /// Storage: ParachainSystem RelevantMessagingState (r:1 w:0) + /// Proof Skipped: ParachainSystem RelevantMessagingState (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: XcmpQueue OutboundXcmpStatus (r:1 w:1) + /// Proof Skipped: XcmpQueue OutboundXcmpStatus (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: XcmpQueue OutboundXcmpMessages (r:0 w:1) + /// Proof Skipped: XcmpQueue OutboundXcmpMessages (max_values: None, max_size: None, mode: Measured) + fn ping_via_bridge() -> Weight { + // Proof Size summary in bytes: + // Measured: `337` + // Estimated: `18918` + // Minimum execution time: 57_858_000 picoseconds. + Weight::from_parts(59_418_000, 0) + .saturating_add(Weight::from_parts(0, 18918)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn add_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `7491` + // Minimum execution time: 16_403_000 picoseconds. + Weight::from_parts(16_675_000, 0) + .saturating_add(Weight::from_parts(0, 7491)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + fn remove_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `6002` + // Minimum execution time: 13_993_000 picoseconds. + Weight::from_parts(14_352_000, 0) + .saturating_add(Weight::from_parts(0, 6002)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + fn update_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `6002` + // Minimum execution time: 18_944_000 picoseconds. + Weight::from_parts(19_371_000, 0) + .saturating_add(Weight::from_parts(0, 6002)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn add_universal_alias() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn remove_universal_alias() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn add_reserve_location() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn remove_reserve_location() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/parachains/runtimes/assets/statemine/src/xcm_config.rs b/parachains/runtimes/assets/statemine/src/xcm_config.rs index 45f95ee07d4..81a8347d79b 100644 --- a/parachains/runtimes/assets/statemine/src/xcm_config.rs +++ b/parachains/runtimes/assets/statemine/src/xcm_config.rs @@ -14,9 +14,9 @@ // limitations under the License. use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - TrustBackedAssetsInstance, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, BridgeTransfer, + ForeignAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; use assets_common::matching::{ FromSiblingParachain, IsForeignConcreteAsset, StartsWith, StartsWithExplicitGlobalConsensus, @@ -42,7 +42,7 @@ use xcm_builder::{ FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, WeightInfoBounds, WithComputedOrigin, + UnpaidRemoteExporter, UsingComponents, WeightInfoBounds, WithComputedOrigin, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -218,6 +218,7 @@ impl Contains for SafeCallFilter { RuntimeCall::XcmpQueue(..) | RuntimeCall::DmpQueue(..) | RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) | + RuntimeCall::BridgeTransfer(..) | RuntimeCall::Assets( pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } | @@ -500,3 +501,107 @@ impl BenchmarkHelper for XcmBenchmarkHelper { MultiLocation { parents: 1, interior: X1(Parachain(id)) } } } + +/// Bridge router, which wraps and sends xcm to BridgeHub to be delivered to the different GlobalConsensus +pub type BridgeXcmSender = UnpaidRemoteExporter; + +/// Benchmarks helper for over-bridge transfer pallet. +#[cfg(feature = "runtime-benchmarks")] +pub struct BridgeTransferBenchmarksHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl BridgeTransferBenchmarksHelper { + /// Parachain at the other side of the bridge that we're connected to. + fn allowed_target_location() -> MultiLocation { + MultiLocation::new(2, X2(GlobalConsensus(Polkadot), Parachain(1000))) + } + + /// Identifier of the sibling bridge-hub parachain. + fn bridge_hub_para_id() -> u32 { + 1002 + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_bridge_transfer::BenchmarkHelper for BridgeTransferBenchmarksHelper { + fn bridge_config() -> Option<(NetworkId, pallet_bridge_transfer::BridgeConfig)> { + Some(( + Polkadot, + pallet_bridge_transfer::BridgeConfig { + bridge_location: (Parent, Parachain(Self::bridge_hub_para_id())).into(), + // Right now `UnpaidRemoteExporter` is used to send XCM messages and it requires + // fee to be `None`. If we're going to change that (are we?), then we should replace + // this `None` with `Some(Self::make_asset(crate::ExistentialDeposit::get()))` + bridge_location_fee: None, + allowed_target_location: Self::allowed_target_location(), + max_target_location_fee: None, + }, + )) + } + + fn prepare_asset_transfer( + ) -> Option<(RuntimeOrigin, xcm::VersionedMultiAssets, xcm::VersionedMultiLocation)> { + use frame_support::traits::Currency; + + // our `BridgeXcmSender` assumes that the HRMP channel is opened between this + // parachain and the sibling bridge-hub parachain + cumulus_pallet_parachain_system::Pallet::::open_outbound_hrmp_channel_for_benchmarks( + Self::bridge_hub_para_id().into(), + ); + + // sender account + let sender_account = AccountId::from([42u8; 32]); + + // We need root origin to create asset + let minimum_asset_balance = 3333333_u128; + let local_asset_id = 1; + frame_support::assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + local_asset_id.into(), + sender_account.clone().into(), + true, + minimum_asset_balance + )); + + // We mint enough asset for the account to exist for assets + frame_support::assert_ok!(Assets::mint( + RuntimeOrigin::signed(sender_account.clone()), + local_asset_id.into(), + sender_account.clone().into(), + minimum_asset_balance * 4 + )); + + // deposit enough funds to the sender account + let existential_deposit = crate::ExistentialDeposit::get(); + let _ = Balances::deposit_creating(&sender_account, existential_deposit * 10); + + // finally - prepare assets and destination (pallet_assets is worse than pallet_balances) + use xcm_executor::traits::Convert; + let asset_id_location = assets_common::AssetIdForTrustBackedAssetsConvert::< + TrustBackedAssetsPalletLocation, + >::reverse_ref(local_asset_id) + .unwrap(); + let asset: MultiAsset = (Concrete(asset_id_location), minimum_asset_balance * 2).into(); + + let assets = xcm::VersionedMultiAssets::V3(asset.into()); + let destination = xcm::VersionedMultiLocation::V3(Self::allowed_target_location()); + + Some((RuntimeOrigin::signed(sender_account), assets, destination)) + } + + fn prepare_ping_transfer() -> Option<(RuntimeOrigin, xcm::VersionedMultiLocation)> { + // our `BridgeXcmSender` assumes that the HRMP channel is opened between this + // parachain and the sibling bridge-hub parachain + cumulus_pallet_parachain_system::Pallet::::open_outbound_hrmp_channel_for_benchmarks( + Self::bridge_hub_para_id().into(), + ); + + // sender account + let sender_account = AccountId::from([42u8; 32]); + + // finally - prepare destination + let destination = xcm::VersionedMultiLocation::V3(Self::allowed_target_location()); + + Some((RuntimeOrigin::signed(sender_account), destination)) + } +} diff --git a/parachains/runtimes/assets/statemine/tests/tests.rs b/parachains/runtimes/assets/statemine/tests/tests.rs index b9001a35a99..079356a8658 100644 --- a/parachains/runtimes/assets/statemine/tests/tests.rs +++ b/parachains/runtimes/assets/statemine/tests/tests.rs @@ -12,10 +12,13 @@ use statemine_runtime::xcm_config::{ }; pub use statemine_runtime::{ constants::fee::WeightToFee, - xcm_config::{CheckingAccount, ForeignCreatorsSovereignAccountOf, XcmConfig}, + xcm_config::{ + CheckingAccount, ForeignCreatorsSovereignAccountOf, LocationToAccountId, XcmConfig, + }, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, - MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, - RuntimeEvent, SessionKeys, System, TrustBackedAssetsInstance, + MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, PolkadotXcm, Runtime, + RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, System, TrustBackedAssetsInstance, + XcmpQueue, }; use xcm::latest::prelude::*; use xcm_executor::traits::{Convert, Identity, JustTry, WeightTrader}; @@ -620,3 +623,46 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p assert_eq!(ForeignAssets::asset_ids().collect::>().len(), 1); }) ); + +asset_test_utils::include_can_governance_change_bridge_transfer_out_configuration!( + Runtime, + XcmConfig, + asset_test_utils::CollatorSessionKeys::new( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } + ), + Box::new(|call| RuntimeCall::BridgeTransfer(call).encode()), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::BridgeTransfer(event)) => Some(event), + _ => None, + } + }) +); + +asset_test_utils::include_initiate_transfer_asset_via_bridge_for_native_asset_works!( + Runtime, + XcmConfig, + ParachainSystem, + XcmpQueue, + LocationToAccountId, + asset_test_utils::CollatorSessionKeys::new( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } + ), + ExistentialDeposit::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::BridgeTransfer(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }) +); diff --git a/parachains/runtimes/assets/test-utils/Cargo.toml b/parachains/runtimes/assets/test-utils/Cargo.toml index 8a1ce0e6e3c..b6406cbb5c9 100644 --- a/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/parachains/runtimes/assets/test-utils/Cargo.toml @@ -31,6 +31,7 @@ cumulus-primitives-core = { path = "../../../../primitives/core", default-featur cumulus-primitives-parachain-inherent = { path = "../../../../primitives/parachain-inherent", default-features = false } cumulus-test-relay-sproof-builder = { path = "../../../../test/relay-sproof-builder", default-features = false } parachain-info = { path = "../../../../parachains/pallets/parachain-info", default-features = false } +pallet-bridge-transfer = { path = "../../../pallets/bridge-transfer", default-features = false } # Polkadot xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } @@ -58,17 +59,18 @@ std = [ "cumulus-pallet-parachain-system/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-xcm/std", "assets-common/std", "parachains-common/std", - "parachain-info/std", - "polkadot-parachain/std", + "parachain-info/std", + "polkadot-parachain/std", "sp-consensus-aura/std", "sp-io/std", "sp-runtime/std", "sp-std/std", "xcm/std", "xcm-executor/std", - "pallet-xcm/std", + "pallet-bridge-transfer/std", "cumulus-pallet-xcmp-queue/std", "cumulus-pallet-dmp-queue/std", ] diff --git a/parachains/runtimes/assets/test-utils/src/lib.rs b/parachains/runtimes/assets/test-utils/src/lib.rs index 1e0b31f18a3..db5e28fc255 100644 --- a/parachains/runtimes/assets/test-utils/src/lib.rs +++ b/parachains/runtimes/assets/test-utils/src/lib.rs @@ -1,22 +1,26 @@ -use sp_std::marker::PhantomData; - +use codec::DecodeLimit; use cumulus_primitives_core::{AbridgedHrmpChannel, ParaId, PersistedValidationData}; use cumulus_primitives_parachain_inherent::ParachainInherentData; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ - dispatch::{DispatchResult, RawOrigin, UnfilteredDispatchable}, + dispatch::{RawOrigin, UnfilteredDispatchable}, inherent::{InherentData, ProvideInherent}, - traits::{GenesisBuild, OriginTrait}, - weights::Weight, + traits::GenesisBuild, }; +use sp_std::marker::PhantomData; + +use frame_support::{traits::OriginTrait, weights::Weight}; use parachains_common::AccountId; -use polkadot_parachain::primitives::{HrmpChannelId, RelayChainBlockNumber}; +use polkadot_parachain::primitives::{HrmpChannelId, RelayChainBlockNumber, XcmpMessageFormat}; + +use frame_support::dispatch::DispatchResult; use sp_consensus_aura::AURA_ENGINE_ID; use sp_core::Encode; use sp_runtime::{Digest, DigestItem}; use xcm::{ latest::{MultiAsset, MultiLocation, XcmContext, XcmHash}, prelude::*, + VersionedXcm, MAX_XCM_DECODE_DEPTH, }; use xcm_executor::{traits::TransactAsset, Assets}; @@ -408,3 +412,28 @@ pub fn mock_open_hrmp_channel< .dispatch_bypass_filter(RawOrigin::None.into()) .expect("dispatch succeeded"); } + +impl + RuntimeHelper +{ + pub fn take_xcm(sent_to_para_id: ParaId) -> Option> { + match HrmpChannelSource::take_outbound_messages(10)[..] { + [(para_id, ref mut xcm_message_data)] if para_id.eq(&sent_to_para_id.into()) => { + let mut xcm_message_data = &xcm_message_data[..]; + // decode + let _ = XcmpMessageFormat::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_message_data, + ) + .expect("valid format"); + VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_message_data, + ) + .map(|x| Some(x)) + .expect("result with xcm") + }, + _ => return None, + } + } +} diff --git a/parachains/runtimes/assets/test-utils/src/test_cases.rs b/parachains/runtimes/assets/test-utils/src/test_cases.rs index 954ff0d7589..fcb1bdbbbfa 100644 --- a/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -16,13 +16,14 @@ //! Module contains predefined test-case scenarios for `Runtime` with various assets. use crate::{ - assert_metadata, assert_total, AccountIdOf, BalanceOf, ExtBuilder, RuntimeHelper, - SessionKeysOf, ValidatorIdOf, XcmReceivedFrom, + assert_metadata, assert_total, mock_open_hrmp_channel, AccountIdOf, BalanceOf, ExtBuilder, + RuntimeHelper, SessionKeysOf, ValidatorIdOf, XcmReceivedFrom, }; use codec::Encode; +use cumulus_primitives_core::XcmpMessageSource; use frame_support::{ assert_noop, assert_ok, - traits::{fungibles::InspectEnumerable, Get, OriginTrait}, + traits::{fungibles::InspectEnumerable, Contains, Currency, Get, OriginTrait}, weights::Weight, }; use parachains_common::Balance; @@ -30,7 +31,10 @@ use sp_runtime::{ traits::{StaticLookup, Zero}, DispatchError, Saturating, }; -use xcm::latest::prelude::*; +use xcm::{ + latest::prelude::*, CreateMatcher, MatchXcm, VersionedMultiAsset, VersionedMultiAssets, + VersionedMultiLocation, +}; use xcm_executor::{traits::Convert, XcmExecutor}; pub struct CollatorSessionKeys< @@ -732,7 +736,7 @@ macro_rules! include_asset_transactor_transfer_with_local_consensus_currency_wor } ); -///Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain currency +/// Test-case makes sure that `Runtime`'s `xcm::AssetTransactor` can handle native relay chain currency pub fn asset_transactor_transfer_with_pallet_assets_instance_works< Runtime, XcmConfig, @@ -992,6 +996,7 @@ macro_rules! include_asset_transactor_transfer_with_pallet_assets_instance_works } ); +/// Test-case makes sure that `Runtime`'s can create and manage `ForeignAssets` pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_works< Runtime, XcmConfig, @@ -1323,3 +1328,779 @@ macro_rules! include_create_and_manage_foreign_assets_for_local_consensus_parach } } ); + +/// Test-case makes sure that `Runtime` can manage `bridge_transfer out` configuration by governance +pub fn can_governance_change_bridge_transfer_out_configuration( + collator_session_keys: CollatorSessionKeys, + runtime_call_encode: Box) -> Vec>, + unwrap_pallet_bridge_transfer_event: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_dmp_queue::Config + + pallet_bridge_transfer::Config, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + XcmConfig: xcm_executor::Config, +{ + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .build() + .execute_with(|| { + // bridge cfg data + let bridged_network = ByGenesis([9; 32]); + let bridge_config = pallet_bridge_transfer::BridgeConfig { + bridge_location: (Parent, Parachain(1013)).into(), + bridge_location_fee: None, + allowed_target_location: MultiLocation::new( + 2, + X2(GlobalConsensus(bridged_network), Parachain(1000)), + ), + max_target_location_fee: None, + }; + + // check no cfg + assert!(pallet_bridge_transfer::Pallet::::allowed_exporters(&bridged_network) + .is_none()); + + // governance can add exporter config + assert_ok!(RuntimeHelper::::execute_as_governance( + runtime_call_encode( + pallet_bridge_transfer::Call::::add_exporter_config { + bridged_network, + bridge_config: Box::new(bridge_config.clone()), + } + ), + <::WeightInfo as pallet_bridge_transfer::weights::WeightInfo>::add_exporter_config() + ) + .ensure_complete()); + + assert!(>::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_transfer_event(e.event.encode())) + .any(|e| matches!(e, pallet_bridge_transfer::Event::BridgeAdded))); + { + let cfg = + pallet_bridge_transfer::Pallet::::allowed_exporters(&bridged_network); + assert!(cfg.is_some()); + let cfg = cfg.unwrap(); + assert_eq!(cfg.bridge_location, bridge_config.bridge_location); + assert_eq!(cfg.bridge_location_fee, None); + assert_eq!(cfg.allowed_target_location, bridge_config.allowed_target_location); + assert_eq!(cfg.max_target_location_fee, None); + } + + // governance can update bridge config + let new_bridge_location_fee: MultiAsset = + (Concrete(MultiLocation::parent()), 1_000).into(); + let new_target_location_fee: MultiAsset = + (Concrete(MultiLocation::parent()), 1_000_000).into(); + assert_ok!(RuntimeHelper::::execute_as_governance( + runtime_call_encode( + pallet_bridge_transfer::Call::::update_exporter_config { + bridged_network, + bridge_location_fee: Some(Box::new(VersionedMultiAsset::V3( + new_bridge_location_fee.clone() + ))), + target_location_fee: Some(Box::new(VersionedMultiAsset::V3( + new_target_location_fee.clone() + ))), + } + ), + <::WeightInfo as pallet_bridge_transfer::weights::WeightInfo>::update_exporter_config() + ) + .ensure_complete()); + assert!(>::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_transfer_event(e.event.encode())) + .any(|e| matches!(e, pallet_bridge_transfer::Event::BridgeUpdated))); + { + let cfg = + pallet_bridge_transfer::Pallet::::allowed_exporters(&bridged_network); + assert!(cfg.is_some()); + let cfg = cfg.unwrap(); + assert_eq!(cfg.bridge_location, bridge_config.bridge_location); + assert_eq!(cfg.bridge_location_fee, Some(new_bridge_location_fee)); + assert_eq!(cfg.allowed_target_location, bridge_config.allowed_target_location); + assert_eq!(cfg.max_target_location_fee, Some(new_target_location_fee)); + } + + // governance can remove bridge config + assert_ok!(RuntimeHelper::::execute_as_governance( + runtime_call_encode( + pallet_bridge_transfer::Call::::remove_exporter_config { bridged_network } + ), + <::WeightInfo as pallet_bridge_transfer::weights::WeightInfo>::remove_exporter_config() + ) + .ensure_complete()); + assert!(pallet_bridge_transfer::Pallet::::allowed_exporters(&bridged_network) + .is_none()); + assert!(>::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_transfer_event(e.event.encode())) + .any(|e| matches!(e, pallet_bridge_transfer::Event::BridgeRemoved))); + }) +} + +#[macro_export] +macro_rules! include_can_governance_change_bridge_transfer_out_configuration( + ( + $runtime:path, + $xcm_config:path, + $collator_session_key:expr, + $runtime_call_encode:expr, + $unwrap_pallet_bridge_transfer_event:expr + ) => { + #[test] + fn can_governance_change_bridge_transfer_out_configuration() { + $crate::test_cases::can_governance_change_bridge_transfer_out_configuration::< + $runtime, + $xcm_config, + >( + $collator_session_key, + $runtime_call_encode, + $unwrap_pallet_bridge_transfer_event, + ) + } + } +); + +/// Test-case makes sure that `Runtime` can manage `bridge_transfer in` configuration by governance +pub fn can_governance_change_bridge_transfer_in_configuration( + collator_session_keys: CollatorSessionKeys, + runtime_call_encode: Box) -> Vec>, + unwrap_pallet_bridge_transfer_event: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_dmp_queue::Config + + pallet_bridge_transfer::Config, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + XcmConfig: xcm_executor::Config, +{ + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .build() + .execute_with(|| { + // bridge cfg data + let bridge_location = (Parent, Parachain(1013)).into(); + let alias_junction = GlobalConsensus(ByGenesis([9; 32])); + + // check before + assert!( + !pallet_bridge_transfer::impls::AllowedUniversalAliasesOf::::contains(&( + bridge_location, + alias_junction + )) + ); + + // governance can add bridge config + assert_ok!(RuntimeHelper::::execute_as_governance( + runtime_call_encode( + pallet_bridge_transfer::Call::::add_universal_alias { + location: Box::new(VersionedMultiLocation::V3(bridge_location.clone())), + junction: alias_junction, + } + ), + <::WeightInfo as pallet_bridge_transfer::weights::WeightInfo>::add_universal_alias() + ) + .ensure_complete()); + assert!(>::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_transfer_event(e.event.encode())) + .any(|e| matches!(e, pallet_bridge_transfer::Event::UniversalAliasAdded))); + + // check after + assert!(pallet_bridge_transfer::impls::AllowedUniversalAliasesOf::::contains( + &(bridge_location, alias_junction) + )); + }) +} + +#[macro_export] +macro_rules! include_can_governance_change_bridge_transfer_in_configuration( + ( + $runtime:path, + $xcm_config:path, + $collator_session_key:expr, + $runtime_call_encode:expr, + $unwrap_pallet_bridge_transfer_event:expr + ) => { + #[test] + fn can_governance_change_bridge_transfer_in_configuration() { + $crate::test_cases::can_governance_change_bridge_transfer_in_configuration::< + $runtime, + $xcm_config, + >( + $collator_session_key, + $runtime_call_encode, + $unwrap_pallet_bridge_transfer_event, + ) + } + } +); + +/// Test-case makes sure that `Runtime` can initiate transfer of assets via bridge +pub fn initiate_transfer_asset_via_bridge_for_native_asset_works< + Runtime, + XcmConfig, + HrmpChannelOpener, + HrmpChannelSource, + LocationToAccountId, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + alice_account: AccountIdOf, + unwrap_pallet_bridge_transfer_event: Box< + dyn Fn(Vec) -> Option>, + >, + unwrap_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + pallet_bridge_transfer::Config + + cumulus_pallet_xcmp_queue::Config, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::Balance: From + Into, + XcmConfig: xcm_executor::Config, + LocationToAccountId: Convert>, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + HrmpChannelSource: XcmpMessageSource, +{ + let runtime_para_id = 1000; + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_tracing() + .with_safe_xcm_version(3) + .with_para_id(runtime_para_id.into()) + .build() + .execute_with(|| { + // prepare bridge config + let bridged_network = ByGenesis([6; 32]); + let bridge_hub_para_id = 1013; + let bridge_hub_location = (Parent, Parachain(bridge_hub_para_id)).into(); + let bridge_hub_account = LocationToAccountId::convert_ref(&bridge_hub_location) + .expect("BridgeHub's Sovereign account"); + let target_location_from_different_consensus = + MultiLocation::new(2, X2(GlobalConsensus(bridged_network), Parachain(1000))); + let target_location_fee: MultiAsset = (MultiLocation::parent(), 1_000_000).into(); + let bridge_config = pallet_bridge_transfer::BridgeConfig { + bridge_location: bridge_hub_location, + bridge_location_fee: None, + allowed_target_location: target_location_from_different_consensus, + max_target_location_fee: Some(target_location_fee.clone()), + }; + let balance_to_transfer = 1000_u128; + let native_asset = MultiLocation::parent(); + + // open HRMP to bridge hub + mock_open_hrmp_channel::( + runtime_para_id.into(), + bridge_hub_para_id.into(), + ); + + // drip ED to account + let alice_account_init_balance = existential_deposit + balance_to_transfer.into(); + let _ = >::deposit_creating( + &alice_account, + alice_account_init_balance.clone(), + ); + // SA needs to have at least ED, anyway making reserve fails + let _ = >::deposit_creating( + &bridge_hub_account, + existential_deposit, + ); + + // we just check here, that user remains enough balances after withdraw + // and also we check if `balance_to_transfer` is more than `existential_deposit`, + assert!( + (>::free_balance(&alice_account) - + balance_to_transfer.into()) >= + existential_deposit + ); + // SA has just ED + assert_eq!( + >::free_balance(&bridge_hub_account), + existential_deposit + ); + + // insert bridge config + assert_ok!(>::add_exporter_config( + RuntimeHelper::::root_origin(), + bridged_network, + Box::new(bridge_config), + )); + + // local native asset (pallet_balances) + let assets = MultiAssets::from(MultiAsset { + fun: Fungible(balance_to_transfer.into()), + id: Concrete(native_asset), + }); + + // destination is (some) account from different consensus + let target_destination_account = target_location_from_different_consensus + .clone() + .appended_with(AccountId32 { + network: Some(bridged_network), + id: sp_runtime::AccountId32::new([3; 32]).into(), + }) + .unwrap(); + + // trigger asset transfer + assert_ok!(>::transfer_asset_via_bridge( + RuntimeHelper::::origin_of(alice_account.clone()), + Box::new(VersionedMultiAssets::from(assets.clone())), + Box::new(VersionedMultiLocation::from(target_destination_account.clone())), + )); + + // check alice account decreased + assert_eq!( + >::free_balance(&alice_account), + alice_account_init_balance - balance_to_transfer.into() + ); + // check reserve account increased + assert_eq!( + >::free_balance(&bridge_hub_account), + existential_deposit + balance_to_transfer.into() + ); + + // check events + let mut bridge_transfer_events = >::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_transfer_event(e.event.encode())); + assert!(bridge_transfer_events.any(|r| matches!( + r, + pallet_bridge_transfer::Event::ReserveAssetsDeposited { .. } + ))); + let transfer_initiated_event = bridge_transfer_events.find_map(|e| match e { + pallet_bridge_transfer::Event::TransferInitiated { message_hash, sender_cost } => + Some((message_hash, sender_cost)), + _ => None, + }); + let xcm_hash = match transfer_initiated_event { + Some((message_hash, sender_cost)) => { + assert!(sender_cost.is_none()); + Some(message_hash) + }, + _ => { + assert!(false, "No `TransferInitiated` was fired"); + None + }, + }; + + let mut xcmp_queue_events = >::events() + .into_iter() + .filter_map(|e| unwrap_xcmp_queue_event(e.event.encode())); + let xcm_hash_sent = xcmp_queue_events.find_map(|e| match e { + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { message_hash } => message_hash, + _ => None, + }); + assert_eq!(xcm_hash, xcm_hash_sent); + + // read xcm + let xcm_sent = RuntimeHelper::::take_xcm(bridge_hub_para_id.into()); + assert!(xcm_sent.is_some()); + let mut xcm_sent: Xcm<()> = xcm_sent.unwrap().try_into().expect("versioned xcm"); + println!("{:?}", xcm_sent); + + // check sent XCM ExportMessage to bridge-hub + assert!(xcm_sent + .0 + .matcher() + .match_next_inst(|instr| match instr { + // first instruction is UNpai (because we have explicit unpaid execution on bridge-hub now) + UnpaidExecution { weight_limit, check_origin } + if weight_limit == &Unlimited && check_origin.is_none() => + Ok(()), + _ => Err(()), + }) + .expect("contains UnpaidExecution") + .match_next_inst(|instr| match instr { + // second instruction is ExportMessage + ExportMessage { network, destination, xcm: inner_xcm } => { + assert_eq!(network, &bridged_network); + let (_, target_location_junctions_without_global_consensus) = + target_location_from_different_consensus + .interior + .clone() + .split_global() + .expect("split works"); + assert_eq!( + destination, + &target_location_junctions_without_global_consensus + ); + + let mut reanchored_assets = assets.clone(); + reanchored_assets + .reanchor( + &target_location_from_different_consensus, + XcmConfig::UniversalLocation::get(), + ) + .expect("reanchored assets"); + let mut reanchored_destination_account = target_destination_account.clone(); + reanchored_destination_account + .reanchor( + &target_location_from_different_consensus, + XcmConfig::UniversalLocation::get(), + ) + .expect("reanchored destination account"); + + // match inner xcm + assert!(inner_xcm + .0 + .matcher() + .match_next_inst(|next_instr| match next_instr { + WithdrawAsset(fees) + if fees == &MultiAssets::from(target_location_fee.clone()) => + Ok(()), + _ => Err(()), + }) + .expect("contains WithdrawAsset") + .match_next_inst(|next_instr| match next_instr { + BuyExecution { ref fees, ref weight_limit } + if fees == &target_location_fee && + weight_limit == &Unlimited => + Ok(()), + _ => Err(()), + }) + .expect("contains BuyExecution") + .match_next_inst(|inner_xcm_instr| match inner_xcm_instr { + ReserveAssetDeposited(ref deposited) + if deposited.eq(&reanchored_assets) => + Ok(()), + _ => Err(()), + }) + .expect("contains ReserveAssetDeposited") + .match_next_inst(|inner_xcm_instr| match inner_xcm_instr { + ClearOrigin => Ok(()), + _ => Err(()), + }) + .expect("contains ClearOrigin") + .match_next_inst(|inner_xcm_instr| match inner_xcm_instr { + DepositAsset { assets: filter, ref beneficiary } + if filter == + &MultiAssetFilter::from(reanchored_assets.clone()) && + beneficiary.eq(&reanchored_destination_account) => + Ok(()), + _ => Err(()), + }) + .expect("contains DepositAsset") + .assert_remaining_insts(0) + .is_ok()); + Ok(()) + }, + _ => Err(()), + }) + .is_ok()); + }) +} + +#[macro_export] +macro_rules! include_initiate_transfer_asset_via_bridge_for_native_asset_works( + ( + $runtime:path, + $xcm_config:path, + $hrmp_channel_opener:path, + $hrmp_channel_source:path, + $location_to_account_id:path, + $collator_session_key:expr, + $existential_deposit:expr, + $unwrap_pallet_bridge_transfer_event:expr, + $unwrap_xcmp_queue_event:expr + ) => { + #[test] + fn initiate_transfer_asset_via_bridge_for_native_asset_works() { + const ALICE: [u8; 32] = [1u8; 32]; + let alice_account = parachains_common::AccountId::from(ALICE); + + $crate::test_cases::initiate_transfer_asset_via_bridge_for_native_asset_works::< + $runtime, + $xcm_config, + $hrmp_channel_opener, + $hrmp_channel_source, + $location_to_account_id + >( + $collator_session_key, + $existential_deposit, + alice_account, + $unwrap_pallet_bridge_transfer_event, + $unwrap_xcmp_queue_event + ) + } + } +); + +pub fn receive_reserve_asset_deposited_from_different_consensus_works< + Runtime, + XcmConfig, + LocationToAccountId, + ForeignAssetsPalletInstance, +>( + collator_session_keys: CollatorSessionKeys, + existential_deposit: BalanceOf, + target_account: AccountIdOf, + unwrap_pallet_xcm_event: Box) -> Option>>, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_assets::Config + + pallet_bridge_transfer::Config, + AccountIdOf: Into<[u8; 32]>, + ValidatorIdOf: From>, + BalanceOf: From, + ::AccountId: + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + XcmConfig: xcm_executor::Config, + >::AssetId: + From + Into, + >::AssetIdParameter: + From + Into, + >::Balance: + From + Into, + LocationToAccountId: Convert>, + ForeignAssetsPalletInstance: 'static, +{ + let remote_network_id = ByGenesis([7; 32]); + let remote_parachain_as_origin = MultiLocation { + parents: 2, + interior: X2(GlobalConsensus(remote_network_id), Parachain(1000)), + }; + let remote_parachain_sovereign_account = + LocationToAccountId::convert_ref(remote_parachain_as_origin) + .expect("Sovereign account works"); + let foreign_asset_id_multilocation = + MultiLocation { parents: 2, interior: X1(GlobalConsensus(remote_network_id)) }; + let buy_execution_fee_amount = 50000000000; + let reserve_asset_deposisted = 100_000_000; + + let local_bridge_hub_multilocation = + MultiLocation { parents: 1, interior: X1(Parachain(1014)) }; + + ExtBuilder::::default() + .with_collators(collator_session_keys.collators()) + .with_session_keys(collator_session_keys.session_keys()) + .with_balances(vec![ + ( + remote_parachain_sovereign_account.clone(), + existential_deposit + buy_execution_fee_amount.into(), + ), + (target_account.clone(), existential_deposit), + ]) + .with_tracing() + .build() + .execute_with(|| { + // setup bridge transfer configuration + + // add allowed univeral alias for remote network + assert_ok!(>::add_universal_alias( + RuntimeHelper::::root_origin(), + Box::new(VersionedMultiLocation::V3(local_bridge_hub_multilocation)), + GlobalConsensus(remote_network_id) + )); + // add allowed reserve location + assert_ok!(>::add_reserve_location( + RuntimeHelper::::root_origin(), + Box::new(VersionedMultiLocation::V3(remote_parachain_as_origin)) + )); + + // create foreign asset + let asset_minimum_asset_balance = 1_000_000_u128; + assert_ok!( + >::force_create( + RuntimeHelper::::root_origin(), + foreign_asset_id_multilocation.clone().into(), + remote_parachain_sovereign_account.clone().into(), + false, + asset_minimum_asset_balance.into() + ) + ); + + // check before + assert_eq!( + >::free_balance( + &remote_parachain_sovereign_account + ), + existential_deposit + buy_execution_fee_amount.into() + ); + assert_eq!( + >::free_balance(&target_account), + existential_deposit + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &target_account + ), + 0.into() + ); + + // xcm + let xcm = Xcm(vec![ + UniversalOrigin(GlobalConsensus(remote_network_id)), + DescendOrigin(X1(Parachain(1000))), + // buying execution as sovereign account `remote_parachain_sovereign_account` in *native asset on receiving runtime* + WithdrawAsset(MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(buy_execution_fee_amount), + }])), + BuyExecution { + fees: MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(buy_execution_fee_amount), + }, + weight_limit: Unlimited, + }, + // reserve deposited - assets transferred through bridge - *native asset on sending runtime* + ReserveAssetDeposited(MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(remote_network_id)), + }), + fun: Fungible(reserve_asset_deposisted), + }])), + ClearOrigin, + DepositAsset { + assets: Definite(MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(remote_network_id)), + }), + fun: Fungible(reserve_asset_deposisted), + }])), + beneficiary: MultiLocation { + parents: 0, + interior: X1(AccountId32 { + network: None, + id: target_account.clone().into(), + }), + }, + }, + ]); + + // origin as BridgeHub + let origin = local_bridge_hub_multilocation; + + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + + // execute xcm as XcmpQueue would do + let outcome = XcmExecutor::::execute_xcm( + origin, + xcm, + hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + ); + assert_eq!(outcome.ensure_complete(), Ok(())); + + // check after + assert!( + >::free_balance( + &remote_parachain_sovereign_account + ) < existential_deposit + buy_execution_fee_amount.into() + ); + assert_eq!( + >::free_balance(&target_account), + existential_deposit + ); + assert_eq!( + >::balance( + foreign_asset_id_multilocation.into(), + &target_account + ), + reserve_asset_deposisted.into() + ); + + // check asset trap (because big buy fee) + let mut pallet_xcm_events = >::events() + .into_iter() + .filter_map(|e| unwrap_pallet_xcm_event(e.event.encode())); + assert!(pallet_xcm_events.any(|e| match e { + pallet_xcm::Event::AssetsTrapped(_, trapped_for, _) => { + assert_eq!( + trapped_for, origin, + "We expect trapped assets for origin: {:?}, but it is trapped for: {:?}", + origin, trapped_for + ); + true + }, + _ => false, + })); + }) +} + +#[macro_export] +macro_rules! include_receive_reserve_asset_deposited_from_different_consensus_works( + ( + $runtime:path, + $xcm_config:path, + $location_to_account_id:path, + $assets_pallet_instance:path, + $collator_session_key:expr, + $existential_deposit:expr, + $unwrap_pallet_xcm_event:expr + ) => { + #[test] + fn receive_reserve_asset_deposited_from_different_consensus_works() { + const BOB: [u8; 32] = [2u8; 32]; + let target_account = parachains_common::AccountId::from(BOB); + + $crate::test_cases::receive_reserve_asset_deposited_from_different_consensus_works::< + $runtime, + $xcm_config, + $location_to_account_id, + $assets_pallet_instance + >( + $collator_session_key, + $existential_deposit, + target_account, + $unwrap_pallet_xcm_event + ) + } + } +); diff --git a/parachains/runtimes/assets/westmint/Cargo.toml b/parachains/runtimes/assets/westmint/Cargo.toml index 91b06660dfa..a7f9357cb64 100644 --- a/parachains/runtimes/assets/westmint/Cargo.toml +++ b/parachains/runtimes/assets/westmint/Cargo.toml @@ -72,6 +72,7 @@ pallet-collator-selection = { path = "../../../../pallets/collator-selection", d parachain-info = { path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } assets-common = { path = "../common", default-features = false } +pallet-bridge-transfer = { path = "../../../pallets/bridge-transfer", default-features = false } [dev-dependencies] hex-literal = "0.4.1" @@ -104,6 +105,7 @@ runtime-benchmarks = [ "cumulus-pallet-xcmp-queue/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", "assets-common/runtime-benchmarks", + "pallet-bridge-transfer/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", @@ -130,6 +132,7 @@ try-runtime = [ "pallet-utility/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", + "pallet-bridge-transfer/try-runtime", ] std = [ "codec/std", @@ -185,5 +188,6 @@ std = [ "parachain-info/std", "parachains-common/std", "assets-common/std", + "pallet-bridge-transfer/std", "substrate-wasm-builder", ] diff --git a/parachains/runtimes/assets/westmint/src/lib.rs b/parachains/runtimes/assets/westmint/src/lib.rs index c237c8dc5cf..1543c564184 100644 --- a/parachains/runtimes/assets/westmint/src/lib.rs +++ b/parachains/runtimes/assets/westmint/src/lib.rs @@ -82,7 +82,7 @@ use assets_common::{ use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use xcm_executor::XcmExecutor; -use crate::xcm_config::ForeignCreatorsSovereignAccountOf; +use crate::xcm_config::{ForeignCreatorsSovereignAccountOf, UniversalLocation}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -664,6 +664,31 @@ impl pallet_nfts::Config for Runtime { type Helper = (); } +impl pallet_bridge_transfer::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type UniversalLocation = UniversalLocation; + type WeightInfo = weights::pallet_bridge_transfer::WeightInfo; + type AdminOrigin = AssetsForceOrigin; + type UniversalAliasesLimit = ConstU32<24>; + type ReserveLocationsLimit = ConstU32<8>; + // no transfer allowed out (now) + type AssetTransactor = (); + // no transfer allowed out (now) + type BridgeXcmSender = (); + // no transfer allowed out (now) + type TransferAssetOrigin = + frame_support::traits::NeverEnsureOrigin; + // no transfer allowed out (now) + type MaxAssetsLimit = ConstU8<0>; + // no transfer allowed out (now) + type TransferPingOrigin = + frame_support::traits::NeverEnsureOrigin; + // no transfer allowed out (now) + type PingMessageBuilder = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = xcm_config::BridgeTransferBenchmarksHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where @@ -708,6 +733,7 @@ construct_runtime!( Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, Nfts: pallet_nfts::{Pallet, Call, Storage, Event} = 52, ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 53, + BridgeTransfer: pallet_bridge_transfer::{Pallet, Call, Storage, Event} = 54, } ); @@ -768,6 +794,7 @@ mod benches { [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_bridge_transfer, BridgeTransfer] // XCM [pallet_xcm, PolkadotXcm] // NOTE: Make sure you point to the individual modules below. @@ -1125,7 +1152,15 @@ impl_runtime_apis! { } fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { - Err(BenchmarkError::Skip) + match <::BenchmarkHelper as pallet_bridge_transfer::BenchmarkHelper>::universal_alias() { + Some((location, junction)) => { + >::insert_universal_alias_for_benchmarks( + (location.clone().try_into().unwrap(), junction) + ); + Ok((location.clone().try_into().unwrap(), junction)) + }, + None => Err(BenchmarkError::Skip) + } } fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { diff --git a/parachains/runtimes/assets/westmint/src/weights/mod.rs b/parachains/runtimes/assets/westmint/src/weights/mod.rs index 92af360ced1..518bd88bc2e 100644 --- a/parachains/runtimes/assets/westmint/src/weights/mod.rs +++ b/parachains/runtimes/assets/westmint/src/weights/mod.rs @@ -4,6 +4,7 @@ pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_assets; pub mod pallet_balances; +pub mod pallet_bridge_transfer; pub mod pallet_collator_selection; pub mod pallet_multisig; pub mod pallet_nfts; diff --git a/parachains/runtimes/assets/westmint/src/weights/pallet_bridge_transfer.rs b/parachains/runtimes/assets/westmint/src/weights/pallet_bridge_transfer.rs new file mode 100644 index 00000000000..23de590f2d1 --- /dev/null +++ b/parachains/runtimes/assets/westmint/src/weights/pallet_bridge_transfer.rs @@ -0,0 +1,153 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +//! Autogenerated weights for `pallet_bridge_transfer` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-03-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westmint-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/cumulus/.git/.artifacts/bench.json +// --pallet=pallet_bridge_transfer +// --chain=westmint-dev +// --header=./file_header.txt +// --output=./parachains/runtimes/assets/westmint/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weight functions for `pallet_bridge_transfer`. +pub struct WeightInfo(PhantomData); +impl pallet_bridge_transfer::WeightInfo for WeightInfo { + fn transfer_asset_via_bridge() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn ping_via_bridge() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn add_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `7491` + // Minimum execution time: 15_704_000 picoseconds. + Weight::from_parts(16_139_000, 0) + .saturating_add(Weight::from_parts(0, 7491)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + fn remove_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `6002` + // Minimum execution time: 13_749_000 picoseconds. + Weight::from_parts(14_178_000, 0) + .saturating_add(Weight::from_parts(0, 6002)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedExporters (r:1 w:1) + /// Proof: BridgeTransfer AllowedExporters (max_values: None, max_size: Some(2537), added: 5012, mode: MaxEncodedLen) + fn update_exporter_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `6002` + // Minimum execution time: 18_664_000 picoseconds. + Weight::from_parts(18_982_000, 0) + .saturating_add(Weight::from_parts(0, 6002)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedUniversalAliases (r:1 w:1) + /// Proof: BridgeTransfer AllowedUniversalAliases (max_values: None, max_size: Some(2419), added: 4894, mode: MaxEncodedLen) + fn add_universal_alias() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `5884` + // Minimum execution time: 12_859_000 picoseconds. + Weight::from_parts(19_737_000, 0) + .saturating_add(Weight::from_parts(0, 5884)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedUniversalAliases (r:1 w:1) + /// Proof: BridgeTransfer AllowedUniversalAliases (max_values: None, max_size: Some(2419), added: 4894, mode: MaxEncodedLen) + fn remove_universal_alias() -> Weight { + // Proof Size summary in bytes: + // Measured: `158` + // Estimated: `5884` + // Minimum execution time: 15_939_000 picoseconds. + Weight::from_parts(16_245_000, 0) + .saturating_add(Weight::from_parts(0, 5884)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedReserveLocations (r:1 w:1) + /// Proof: BridgeTransfer AllowedReserveLocations (max_values: Some(1), max_size: Some(4817), added: 5312, mode: MaxEncodedLen) + fn add_reserve_location() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `6302` + // Minimum execution time: 12_764_000 picoseconds. + Weight::from_parts(13_060_000, 0) + .saturating_add(Weight::from_parts(0, 6302)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: BridgeTransfer AllowedReserveLocations (r:1 w:1) + /// Proof: BridgeTransfer AllowedReserveLocations (max_values: Some(1), max_size: Some(4817), added: 5312, mode: MaxEncodedLen) + fn remove_reserve_location() -> Weight { + // Proof Size summary in bytes: + // Measured: `141` + // Estimated: `6302` + // Minimum execution time: 14_613_000 picoseconds. + Weight::from_parts(14_927_000, 0) + .saturating_add(Weight::from_parts(0, 6302)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs index 5daa3acbcce..129cc453eb5 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/mod.rs @@ -215,7 +215,7 @@ impl XcmWeightInfo for WestmintXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - Weight::MAX + XcmGeneric::::universal_origin() } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 67c2b083f89..75b96b2d8e9 100644 --- a/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/parachains/runtimes/assets/westmint/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,13 +17,13 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-03-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-04-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm6`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westmint-dev"), DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet // --template=./templates/xcm-bench-template.hbs @@ -64,8 +64,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `11269` - // Minimum execution time: 355_263_000 picoseconds. - Weight::from_parts(357_327_000, 11269) + // Minimum execution time: 368_187_000 picoseconds. + Weight::from_parts(369_271_000, 11269) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,8 +73,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_068_000 picoseconds. - Weight::from_parts(4_273_000, 0) + // Minimum execution time: 4_064_000 picoseconds. + Weight::from_parts(4_163_000, 0) } // Storage: PolkadotXcm Queries (r:1 w:0) // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) @@ -82,58 +82,58 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `69` // Estimated: `3534` - // Minimum execution time: 11_279_000 picoseconds. - Weight::from_parts(11_626_000, 3534) + // Minimum execution time: 11_590_000 picoseconds. + Weight::from_parts(11_834_000, 3534) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_246_000 picoseconds. - Weight::from_parts(13_425_000, 0) + // Minimum execution time: 14_425_000 picoseconds. + Weight::from_parts(14_713_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_426_000 picoseconds. - Weight::from_parts(4_600_000, 0) + // Minimum execution time: 4_539_000 picoseconds. + Weight::from_parts(4_709_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_838_000 picoseconds. - Weight::from_parts(2_921_000, 0) + // Minimum execution time: 2_933_000 picoseconds. + Weight::from_parts(2_983_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_858_000 picoseconds. - Weight::from_parts(2_981_000, 0) + // Minimum execution time: 2_867_000 picoseconds. + Weight::from_parts(2_957_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_848_000 picoseconds. - Weight::from_parts(2_922_000, 0) + // Minimum execution time: 2_891_000 picoseconds. + Weight::from_parts(2_945_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_732_000 picoseconds. - Weight::from_parts(3_801_000, 0) + // Minimum execution time: 3_783_000 picoseconds. + Weight::from_parts(3_882_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_882_000 picoseconds. - Weight::from_parts(2_971_000, 0) + // Minimum execution time: 2_843_000 picoseconds. + Weight::from_parts(2_959_000, 0) } // Storage: ParachainInfo ParachainId (r:1 w:0) // Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -151,8 +151,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `11269` - // Minimum execution time: 25_538_000 picoseconds. - Weight::from_parts(25_964_000, 11269) + // Minimum execution time: 25_284_000 picoseconds. + Weight::from_parts(25_778_000, 11269) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -162,8 +162,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `126` // Estimated: `3591` - // Minimum execution time: 16_187_000 picoseconds. - Weight::from_parts(16_478_000, 3591) + // Minimum execution time: 16_533_000 picoseconds. + Weight::from_parts(16_974_000, 3591) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -171,8 +171,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_804_000 picoseconds. - Weight::from_parts(2_874_000, 0) + // Minimum execution time: 2_867_000 picoseconds. + Weight::from_parts(3_018_000, 0) } // Storage: PolkadotXcm VersionNotifyTargets (r:1 w:1) // Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) @@ -190,8 +190,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `13320` - // Minimum execution time: 28_208_000 picoseconds. - Weight::from_parts(28_512_000, 13320) + // Minimum execution time: 28_098_000 picoseconds. + Weight::from_parts(28_424_000, 13320) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -201,8 +201,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_021_000 picoseconds. - Weight::from_parts(5_128_000, 0) + // Minimum execution time: 5_062_000 picoseconds. + Weight::from_parts(5_184_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: ParachainInfo ParachainId (r:1 w:0) @@ -221,8 +221,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `11269` - // Minimum execution time: 403_561_000 picoseconds. - Weight::from_parts(404_798_000, 11269) + // Minimum execution time: 413_217_000 picoseconds. + Weight::from_parts(415_362_000, 11269) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -230,36 +230,36 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 122_646_000 picoseconds. - Weight::from_parts(123_057_000, 0) + // Minimum execution time: 130_094_000 picoseconds. + Weight::from_parts(132_683_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_916_000 picoseconds. - Weight::from_parts(14_178_000, 0) + // Minimum execution time: 12_633_000 picoseconds. + Weight::from_parts(12_833_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_025_000 picoseconds. - Weight::from_parts(3_083_000, 0) + // Minimum execution time: 3_012_000 picoseconds. + Weight::from_parts(3_105_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_879_000 picoseconds. - Weight::from_parts(2_947_000, 0) + // Minimum execution time: 2_926_000 picoseconds. + Weight::from_parts(3_030_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_067_000 picoseconds. - Weight::from_parts(3_129_000, 0) + // Minimum execution time: 3_172_000 picoseconds. + Weight::from_parts(3_250_000, 0) } // Storage: ParachainInfo ParachainId (r:1 w:0) // Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -277,8 +277,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `11269` - // Minimum execution time: 29_511_000 picoseconds. - Weight::from_parts(29_922_000, 11269) + // Minimum execution time: 29_409_000 picoseconds. + Weight::from_parts(29_799_000, 11269) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -286,8 +286,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_410_000 picoseconds. - Weight::from_parts(5_531_000, 0) + // Minimum execution time: 5_448_000 picoseconds. + Weight::from_parts(5_613_000, 0) } // Storage: ParachainInfo ParachainId (r:1 w:0) // Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) @@ -305,8 +305,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `75` // Estimated: `11269` - // Minimum execution time: 26_044_000 picoseconds. - Weight::from_parts(26_397_000, 11269) + // Minimum execution time: 25_875_000 picoseconds. + Weight::from_parts(26_241_000, 11269) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -314,35 +314,47 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_950_000 picoseconds. - Weight::from_parts(2_989_000, 0) + // Minimum execution time: 2_880_000 picoseconds. + Weight::from_parts(2_959_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_877_000 picoseconds. - Weight::from_parts(2_928_000, 0) + // Minimum execution time: 2_900_000 picoseconds. + Weight::from_parts(2_972_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_884_000 picoseconds. - Weight::from_parts(2_959_000, 0) + // Minimum execution time: 2_875_000 picoseconds. + Weight::from_parts(2_940_000, 0) + } + // Storage: ParachainInfo ParachainId (r:1 w:0) + // Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + // Storage: BridgeTransfer AllowedUniversalAliases (r:1 w:0) + // Proof: BridgeTransfer AllowedUniversalAliases (max_values: None, max_size: Some(2419), added: 4894, mode: MaxEncodedLen) + pub fn universal_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `158` + // Estimated: `7373` + // Minimum execution time: 10_161_000 picoseconds. + Weight::from_parts(10_414_000, 7373) + .saturating_add(T::DbWeight::get().reads(2)) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_733_000 picoseconds. - Weight::from_parts(2_862_000, 0) + // Minimum execution time: 2_877_000 picoseconds. + Weight::from_parts(2_974_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_917_000 picoseconds. - Weight::from_parts(2_990_000, 0) + // Minimum execution time: 3_055_000 picoseconds. + Weight::from_parts(3_135_000, 0) } } diff --git a/parachains/runtimes/assets/westmint/src/xcm_config.rs b/parachains/runtimes/assets/westmint/src/xcm_config.rs index d9987a627ae..06bf25b6f64 100644 --- a/parachains/runtimes/assets/westmint/src/xcm_config.rs +++ b/parachains/runtimes/assets/westmint/src/xcm_config.rs @@ -14,19 +14,23 @@ // limitations under the License. use super::{ - AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ParachainInfo, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + AccountId, AllPalletsWithSystem, Assets, Authorship, Balance, Balances, ForeignAssets, + ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TrustBackedAssetsInstance, WeightToFee, XcmpQueue, }; -use crate::ForeignAssets; -use assets_common::matching::{ - FromSiblingParachain, IsForeignConcreteAsset, StartsWith, StartsWithExplicitGlobalConsensus, +use assets_common::{ + location_conversion::GlobalConsensusParachainConvert, + matching::{ + FromSiblingParachain, IsDifferentGlobalConsensusConcreteAsset, IsForeignConcreteAsset, + StartsWith, StartsWithExplicitGlobalConsensus, + }, }; use frame_support::{ match_types, parameter_types, - traits::{ConstU32, Contains, Everything, Nothing, PalletInfoAccess}, + traits::{ConstU32, Contains, Everything, PalletInfoAccess}, }; use frame_system::EnsureRoot; +use pallet_bridge_transfer::impls::{AllowedUniversalAliasesOf, IsAllowedReserveOf}; use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, @@ -69,6 +73,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Different global consensus parachain sovereign account + GlobalConsensusParachainConvert, ); /// Means for transacting the native currency on this chain. @@ -216,6 +222,7 @@ impl Contains for SafeCallFilter { pallet_utility::Call::batch { .. } | pallet_utility::Call::batch_all { .. }, ) | + RuntimeCall::BridgeTransfer(..) | RuntimeCall::Assets( pallet_assets::Call::create { .. } | pallet_assets::Call::force_create { .. } | @@ -378,10 +385,12 @@ impl xcm_executor::Config for XcmConfig { type XcmSender = XcmRouter; type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Westmint does not recognize a reserve location for any asset. This does not prevent - // Westmint acting _as_ a reserve location for WND and assets created under `pallet-assets`. + // Westmint is acting _as_ a reserve location for WND and assets created under `pallet-assets`. // For WND, users must use teleport where allowed (e.g. with the Relay Chain). - type IsReserve = (); + type IsReserve = IsAllowedReserveOf< + Runtime, + IsDifferentGlobalConsensusConcreteAsset, + >; // We allow: // - teleportation of WND // - teleportation of sibling parachain's assets (as ForeignCreators) @@ -420,7 +429,7 @@ impl xcm_executor::Config for XcmConfig { type AssetExchanger = (); type FeeManager = (); type MessageExporter = (); - type UniversalAliases = Nothing; + type UniversalAliases = AllowedUniversalAliasesOf; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; } @@ -493,3 +502,55 @@ impl BenchmarkHelper for XcmBenchmarkHelper { MultiLocation { parents: 1, interior: X1(Parachain(id)) } } } + +/// Benchmarks helper for over-bridge transfer pallet. +#[cfg(feature = "runtime-benchmarks")] +pub struct BridgeTransferBenchmarksHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl BridgeTransferBenchmarksHelper { + /// Parachain at the other side of the bridge that we're connected to. + fn allowed_target_location() -> MultiLocation { + MultiLocation::new(2, X2(GlobalConsensus(Kusama), Parachain(1000))) + } + + /// Identifier of the sibling bridge-hub parachain. + fn bridge_hub_para_id() -> u32 { + 1002 + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_bridge_transfer::BenchmarkHelper for BridgeTransferBenchmarksHelper { + fn bridge_config() -> Option<(NetworkId, pallet_bridge_transfer::BridgeConfig)> { + Some(( + Kusama, + pallet_bridge_transfer::BridgeConfig { + bridge_location: (Parent, Parachain(Self::bridge_hub_para_id())).into(), + // TODO: right now `UnpaidRemoteExporter` is used to send XCM messages and it requires + // fee to be `None`. If we're going to change that (are we?), then we should replace + // this `None` with `Some(Self::make_asset(crate::ExistentialDeposit::get()))` + bridge_location_fee: None, + allowed_target_location: Self::allowed_target_location(), + max_target_location_fee: None, + }, + )) + } + + fn universal_alias() -> Option<(xcm::VersionedMultiLocation, Junction)> { + Some(( + xcm::VersionedMultiLocation::V3(MultiLocation { + parents: 1, + interior: X1(Parachain(Self::bridge_hub_para_id())), + }), + GlobalConsensus(Kusama), + )) + } + + fn reserve_location() -> Option { + Some(xcm::VersionedMultiLocation::V3(MultiLocation { + parents: 2, + interior: X2(GlobalConsensus(Kusama), Parachain(1000)), + })) + } +} diff --git a/parachains/runtimes/assets/westmint/tests/tests.rs b/parachains/runtimes/assets/westmint/tests/tests.rs index 3ef09d14e52..2fc700c4b1e 100644 --- a/parachains/runtimes/assets/westmint/tests/tests.rs +++ b/parachains/runtimes/assets/westmint/tests/tests.rs @@ -10,7 +10,9 @@ use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance} use std::convert::Into; pub use westmint_runtime::{ constants::fee::WeightToFee, - xcm_config::{CheckingAccount, TrustBackedAssetsPalletLocation, XcmConfig}, + xcm_config::{ + CheckingAccount, LocationToAccountId, TrustBackedAssetsPalletLocation, XcmConfig, + }, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, ParachainSystem, Runtime, SessionKeys, System, TrustBackedAssetsInstance, }; @@ -626,6 +628,42 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p }) ); +asset_test_utils::include_can_governance_change_bridge_transfer_in_configuration!( + Runtime, + XcmConfig, + asset_test_utils::CollatorSessionKeys::new( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } + ), + Box::new(|call| RuntimeCall::BridgeTransfer(call).encode()), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::BridgeTransfer(event)) => Some(event), + _ => None, + } + }) +); + +asset_test_utils::include_receive_reserve_asset_deposited_from_different_consensus_works!( + Runtime, + XcmConfig, + LocationToAccountId, + ForeignAssetsInstance, + asset_test_utils::CollatorSessionKeys::new( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } + ), + ExistentialDeposit::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }) +); + #[test] fn plain_receive_teleported_asset_works() { ExtBuilder::::default() diff --git a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index a31171ba609..48efe425efd 100644 --- a/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -223,3 +223,29 @@ mod bridge_hub_wococo_tests { bridge_hub_wococo_config::DEFAULT_XCM_LANE_TO_BRIDGE_HUB_ROCOCO ); } +bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( + Runtime, + XcmConfig, + CheckingAccount, + WeightToFee, + ParachainSystem, + bridge_hub_test_utils::CollatorSessionKeys::new( + AccountId::from(ALICE), + AccountId::from(ALICE), + SessionKeys { aura: AuraId::from(sp_core::sr25519::Public::from_raw(ALICE)) } + ), + ExistentialDeposit::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + 1013 +); diff --git a/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs b/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs index 5cdc2af9968..3d6aac7b62d 100644 --- a/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs +++ b/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs @@ -489,3 +489,5 @@ mod test_data { GRABBED_HAUL_BLOB_PAYLOAD.with(|r| r.take().expect("Encoded message should be here")) } } +// Re-export test_cases from assets +pub use asset_test_utils::{include_teleports_for_native_asset_works, CollatorSessionKeys}; diff --git a/scripts/generate_hex_encoded_call/index.js b/scripts/generate_hex_encoded_call/index.js index b135622c335..60cf6451bd4 100644 --- a/scripts/generate_hex_encoded_call/index.js +++ b/scripts/generate_hex_encoded_call/index.js @@ -110,33 +110,33 @@ if (!process.argv[2] || !process.argv[3]) { } const type = process.argv[2]; -const rpcEnpoint = process.argv[3]; +const rpcEndpoint = process.argv[3]; const output = process.argv[4]; const inputArgs = process.argv.slice(5, process.argv.length); console.log(`Generating hex-encoded call data for:`); console.log(` type: ${type}`); -console.log(` rpcEnpoint: ${rpcEnpoint}`); +console.log(` rpcEndpoint: ${rpcEndpoint}`); console.log(` output: ${output}`); console.log(` inputArgs: ${inputArgs}`); switch (type) { case 'remark-with-event': - remarkWithEvent(rpcEnpoint, output); + remarkWithEvent(rpcEndpoint, output); break; case 'add-exporter-config': - addExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]); + addExporterConfig(rpcEndpoint, output, inputArgs[0], inputArgs[1]); break; case 'remove-exporter-config': - removeExporterConfig(rpcEnpoint, output, inputArgs[0], inputArgs[1]); + removeExporterConfig(rpcEndpoint, output, inputArgs[0], inputArgs[1]); break; case 'add-universal-alias': - addUniversalAlias(rpcEnpoint, output, inputArgs[0], inputArgs[1]); + addUniversalAlias(rpcEndpoint, output, inputArgs[0], inputArgs[1]); break; case 'add-reserve-location': - addReserveLocation(rpcEnpoint, output, inputArgs[0]); + addReserveLocation(rpcEndpoint, output, inputArgs[0]); break; case 'force-create-asset': - forceCreateAsset(rpcEnpoint, output, inputArgs[0], inputArgs[1], inputArgs[2], inputArgs[3]); + forceCreateAsset(rpcEndpoint, output, inputArgs[0], inputArgs[1], inputArgs[2], inputArgs[3]); break; case 'check': console.log(`Checking nodejs installation, if you see this everything is ready!`); diff --git a/scripts/scale_encode_genesis/index.js b/scripts/scale_encode_genesis/index.js index f612e6da79d..e11289e7844 100644 --- a/scripts/scale_encode_genesis/index.js +++ b/scripts/scale_encode_genesis/index.js @@ -26,7 +26,7 @@ if (!process.argv[2] || !process.argv[3]) { const input = process.argv[2]; const output = process.argv[3]; // default to localhost and the default Substrate port -const rpcEnpoint = process.argv[4] || "ws://localhost:9944"; +const rpcEndpoint = process.argv[4] || "ws://localhost:9944"; console.log("Processing", input, output); fs.readFile(input, "utf8", (err, data) => { @@ -38,8 +38,8 @@ fs.readFile(input, "utf8", (err, data) => { const genesis = JSON.parse(data); console.log("loaded genesis, length = ", genesis.length); - console.log(`Connecting to RPC endpoint: ${rpcEnpoint}`); - connect(rpcEnpoint) + console.log(`Connecting to RPC endpoint: ${rpcEndpoint}`); + connect(rpcEndpoint) .then((api) => { console.log('Connected'); const setStorage = api.tx.system.setStorage(genesis);