From d359108d625e25bfea49104a85e3b40520eb232c Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 11 Nov 2024 10:34:58 +0800 Subject: [PATCH 01/81] Merge from master --- Cargo.lock | 120 +- Cargo.toml | 12 +- .../pallets/inbound-queue-v2/Cargo.toml | 93 + .../pallets/inbound-queue-v2/README.md | 3 + .../inbound-queue-v2/fixtures/Cargo.toml | 34 + .../inbound-queue-v2/fixtures/src/lib.rs | 7 + .../fixtures/src/register_token.rs | 97 + .../fixtures/src/send_token.rs | 95 + .../fixtures/src/send_token_to_penpal.rs | 95 + .../inbound-queue-v2/src/benchmarking/mod.rs | 53 + .../pallets/inbound-queue-v2/src/envelope.rs | 50 + .../pallets/inbound-queue-v2/src/lib.rs | 378 ++++ .../pallets/inbound-queue-v2/src/mock.rs | 362 ++++ .../pallets/inbound-queue-v2/src/test.rs | 245 +++ .../pallets/inbound-queue-v2/src/weights.rs | 31 + .../pallets/inbound-queue/src/lib.rs | 2 +- .../pallets/inbound-queue/src/mock.rs | 2 +- .../pallets/outbound-queue-v2/Cargo.toml | 92 + .../pallets/outbound-queue-v2/README.md | 3 + .../outbound-queue-v2/runtime-api/Cargo.toml | 38 + .../outbound-queue-v2/runtime-api/README.md | 6 + .../outbound-queue-v2/runtime-api/src/lib.rs | 23 + .../pallets/outbound-queue-v2/src/api.rs | 68 + .../outbound-queue-v2/src/benchmarking.rs | 85 + .../pallets/outbound-queue-v2/src/envelope.rs | 47 + .../pallets/outbound-queue-v2/src/lib.rs | 445 +++++ .../pallets/outbound-queue-v2/src/mock.rs | 202 ++ .../src/process_message_impl.rs | 25 + .../src/send_message_impl.rs | 66 + .../pallets/outbound-queue-v2/src/test.rs | 273 +++ .../pallets/outbound-queue-v2/src/types.rs | 23 + .../pallets/outbound-queue-v2/src/weights.rs | 89 + .../pallets/outbound-queue/Cargo.toml | 4 +- .../outbound-queue/merkle-tree/README.md | 4 - .../outbound-queue/runtime-api/Cargo.toml | 4 +- .../outbound-queue/runtime-api/src/lib.rs | 4 +- .../pallets/outbound-queue/src/api.rs | 4 +- .../outbound-queue/src/benchmarking.rs | 2 +- .../pallets/outbound-queue/src/lib.rs | 5 +- .../pallets/outbound-queue/src/mock.rs | 2 +- .../outbound-queue/src/send_message_impl.rs | 4 +- .../pallets/outbound-queue/src/test.rs | 5 +- .../pallets/outbound-queue/src/types.rs | 4 +- bridges/snowbridge/pallets/system/src/lib.rs | 104 +- bridges/snowbridge/pallets/system/src/mock.rs | 6 +- bridges/snowbridge/primitives/core/Cargo.toml | 4 + bridges/snowbridge/primitives/core/src/lib.rs | 3 + .../primitives/core/src/outbound.rs | 475 ----- .../primitives/core/src/outbound/mod.rs | 49 + .../primitives/core/src/outbound/v1.rs | 440 ++++ .../primitives/core/src/outbound/v2.rs | 348 ++++ .../snowbridge/primitives/core/src/reward.rs | 15 + .../merkle-tree/Cargo.toml | 14 +- .../primitives/merkle-tree/README.md | 3 + .../merkle-tree/src/lib.rs | 0 .../snowbridge/primitives/router/Cargo.toml | 3 + .../primitives/router/src/inbound/mod.rs | 458 +---- .../primitives/router/src/inbound/tests.rs | 83 - .../primitives/router/src/inbound/v1.rs | 520 +++++ .../primitives/router/src/inbound/v2.rs | 520 +++++ .../primitives/router/src/outbound/mod.rs | 424 +--- .../primitives/router/src/outbound/tests.rs | 1274 ------------ .../primitives/router/src/outbound/v1.rs | 1703 ++++++++++++++++ .../primitives/router/src/outbound/v2.rs | 1777 +++++++++++++++++ .../runtime/runtime-common/src/tests.rs | 5 +- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../bridge-hub-westend/src/tests/mod.rs | 1 + .../src/tests/snowbridge.rs | 17 +- .../src/tests/snowbridge_v2.rs | 314 +++ .../asset-hub-westend/src/xcm_config.rs | 29 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../src/bridge_to_ethereum_config.rs | 6 +- .../src/genesis_config_presets.rs | 1 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 5 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 12 + .../src/bridge_to_ethereum_config.rs | 74 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 22 +- .../bridge-hub-westend/src/weights/mod.rs | 2 + .../snowbridge_pallet_inbound_queue_v2.rs | 69 + .../snowbridge_pallet_outbound_queue_v2.rs | 98 + .../bridge-hub-westend/src/xcm_config.rs | 9 +- .../bridge-hubs/common/src/message_queue.rs | 14 +- umbrella/Cargo.toml | 8 +- umbrella/src/lib.rs | 4 - 84 files changed, 9322 insertions(+), 2800 deletions(-) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/README.md create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs create mode 100755 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs create mode 100755 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/README.md create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/process_message_impl.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs create mode 100644 bridges/snowbridge/pallets/outbound-queue-v2/src/weights.rs delete mode 100644 bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md delete mode 100644 bridges/snowbridge/primitives/core/src/outbound.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/mod.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/v1.rs create mode 100644 bridges/snowbridge/primitives/core/src/outbound/v2.rs create mode 100644 bridges/snowbridge/primitives/core/src/reward.rs rename bridges/snowbridge/{pallets/outbound-queue => primitives}/merkle-tree/Cargo.toml (76%) create mode 100644 bridges/snowbridge/primitives/merkle-tree/README.md rename bridges/snowbridge/{pallets/outbound-queue => primitives}/merkle-tree/src/lib.rs (100%) delete mode 100644 bridges/snowbridge/primitives/router/src/inbound/tests.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/v1.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/v2.rs delete mode 100644 bridges/snowbridge/primitives/router/src/outbound/tests.rs create mode 100644 bridges/snowbridge/primitives/router/src/outbound/v1.rs create mode 100644 bridges/snowbridge/primitives/router/src/outbound/v2.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue_v2.rs diff --git a/Cargo.lock b/Cargo.lock index 1e1c902df0e1..05f30ad94495 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2322,6 +2322,7 @@ dependencies = [ "serde_json", "snowbridge-beacon-primitives", "snowbridge-core", + "snowbridge-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-inbound-queue", @@ -2439,6 +2440,7 @@ dependencies = [ "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-router-primitives", "sp-core 28.0.0", @@ -2514,10 +2516,14 @@ dependencies = [ "serde_json", "snowbridge-beacon-primitives", "snowbridge-core", + "snowbridge-merkle-tree", "snowbridge-outbound-queue-runtime-api", + "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-inbound-queue", + "snowbridge-pallet-inbound-queue-v2", "snowbridge-pallet-outbound-queue", + "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", @@ -15656,7 +15662,6 @@ dependencies = [ "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", - "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", @@ -21137,6 +21142,8 @@ dependencies = [ name = "snowbridge-core" version = "0.2.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "ethabi-decode", "frame-support", "frame-system", @@ -21179,6 +21186,21 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "snowbridge-merkle-tree" +version = "0.2.0" +dependencies = [ + "array-bytes", + "hex", + "hex-literal", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-runtime 31.0.1", + "sp-tracing 16.0.0", +] + [[package]] name = "snowbridge-milagro-bls" version = "1.5.4" @@ -21195,30 +21217,29 @@ dependencies = [ ] [[package]] -name = "snowbridge-outbound-queue-merkle-tree" -version = "0.3.0" +name = "snowbridge-outbound-queue-runtime-api" +version = "0.2.0" dependencies = [ - "array-bytes", - "hex", - "hex-literal", + "frame-support", "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-crypto-hashing 0.1.0", - "sp-runtime 31.0.1", - "sp-tracing 16.0.0", + "snowbridge-core", + "snowbridge-merkle-tree", + "sp-api 26.0.0", + "sp-std 14.0.0", ] [[package]] -name = "snowbridge-outbound-queue-runtime-api" +name = "snowbridge-outbound-queue-runtime-api-v2" version = "0.2.0" dependencies = [ "frame-support", "parity-scale-codec", + "scale-info", "snowbridge-core", - "snowbridge-outbound-queue-merkle-tree", + "snowbridge-merkle-tree", "sp-api 26.0.0", "sp-std 14.0.0", + "staging-xcm", ] [[package]] @@ -21299,6 +21320,46 @@ dependencies = [ "sp-std 14.0.0", ] +[[package]] +name = "snowbridge-pallet-inbound-queue-fixtures-v2" +version = "0.10.0" +dependencies = [ + "hex-literal", + "snowbridge-beacon-primitives", + "snowbridge-core", + "sp-core 28.0.0", + "sp-std 14.0.0", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue-v2" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-pallet-ethereum-client", + "snowbridge-pallet-inbound-queue-fixtures-v2", + "snowbridge-router-primitives", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-executor", +] + [[package]] name = "snowbridge-pallet-outbound-queue" version = "0.2.0" @@ -21313,13 +21374,43 @@ dependencies = [ "scale-info", "serde", "snowbridge-core", - "snowbridge-outbound-queue-merkle-tree", + "snowbridge-merkle-tree", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + +[[package]] +name = "snowbridge-pallet-outbound-queue-v2" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "bridge-hub-common", + "ethabi-decode", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "pallet-message-queue", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-core", + "snowbridge-merkle-tree", + "snowbridge-router-primitives", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-keyring", "sp-runtime 31.0.1", "sp-std 14.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] @@ -21363,6 +21454,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm", + "staging-xcm-builder", "staging-xcm-executor", ] diff --git a/Cargo.toml b/Cargo.toml index c00276724333..dad503a31fc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,15 +49,19 @@ members = [ "bridges/snowbridge/pallets/ethereum-client", "bridges/snowbridge/pallets/ethereum-client/fixtures", "bridges/snowbridge/pallets/inbound-queue", + "bridges/snowbridge/pallets/inbound-queue-v2", + "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", "bridges/snowbridge/pallets/inbound-queue/fixtures", "bridges/snowbridge/pallets/outbound-queue", - "bridges/snowbridge/pallets/outbound-queue/merkle-tree", + "bridges/snowbridge/pallets/outbound-queue-v2", + "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/outbound-queue/runtime-api", "bridges/snowbridge/pallets/system", "bridges/snowbridge/pallets/system/runtime-api", "bridges/snowbridge/primitives/beacon", "bridges/snowbridge/primitives/core", "bridges/snowbridge/primitives/ethereum", + "bridges/snowbridge/primitives/merkle-tree", "bridges/snowbridge/primitives/router", "bridges/snowbridge/runtime/runtime-common", "bridges/snowbridge/runtime/test-common", @@ -1222,13 +1226,17 @@ smoldot-light = { version = "0.9.0", default-features = false } snowbridge-beacon-primitives = { path = "bridges/snowbridge/primitives/beacon", default-features = false } snowbridge-core = { path = "bridges/snowbridge/primitives/core", default-features = false } snowbridge-ethereum = { path = "bridges/snowbridge/primitives/ethereum", default-features = false } -snowbridge-outbound-queue-merkle-tree = { path = "bridges/snowbridge/pallets/outbound-queue/merkle-tree", default-features = false } +snowbridge-merkle-tree = { path = "bridges/snowbridge/primitives/merkle-tree", default-features = false } snowbridge-outbound-queue-runtime-api = { path = "bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-outbound-queue-runtime-api-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", default-features = false } snowbridge-pallet-ethereum-client = { path = "bridges/snowbridge/pallets/ethereum-client", default-features = false } snowbridge-pallet-ethereum-client-fixtures = { path = "bridges/snowbridge/pallets/ethereum-client/fixtures", default-features = false } snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-queue", default-features = false } snowbridge-pallet-inbound-queue-fixtures = { path = "bridges/snowbridge/pallets/inbound-queue/fixtures", default-features = false } +snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", default-features = false } +snowbridge-pallet-inbound-queue-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-pallet-outbound-queue-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } snowbridge-runtime-common = { path = "bridges/snowbridge/runtime/runtime-common", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml new file mode 100644 index 000000000000..d212b18d2d54 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -0,0 +1,93 @@ +[package] +name = "snowbridge-pallet-inbound-queue-v2" +description = "Snowbridge Inbound Queue Pallet V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { optional = true, workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +hex-literal = { optional = true, workspace = true, default-features = true } +log = { workspace = true } +alloy-primitives = { features = ["rlp"], workspace = true } +alloy-sol-types = { workspace = true } + +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } + +xcm = { workspace = true } +xcm-executor = { workspace = true } + +snowbridge-core = { workspace = true } +snowbridge-router-primitives = { workspace = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-pallet-inbound-queue-fixtures-v2 = { optional = true, workspace = true } + +[dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-sol-types/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-pallet-inbound-queue-fixtures-v2?/std", + "snowbridge-router-primitives/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-fixtures-v2/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "snowbridge-pallet-ethereum-client/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/README.md new file mode 100644 index 000000000000..cc2f7c636e68 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue + +Reads messages from Ethereum and sends it to intended destination on Polkadot, using XCM. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml new file mode 100644 index 000000000000..ea30fdddb553 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snowbridge-pallet-inbound-queue-fixtures-v2" +description = "Snowbridge Inbound Queue Test Fixtures V2" +version = "0.10.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex-literal = { workspace = true, default-features = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-beacon-primitives = { workspace = true } + +[features] +default = ["std"] +std = [ + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "sp-core/std", + "sp-std/std", +] +runtime-benchmarks = [ + "snowbridge-core/runtime-benchmarks", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs new file mode 100644 index 000000000000..00adcdfa186a --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod register_token; +pub mod send_token; +pub mod send_token_to_penpal; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs new file mode 100644 index 000000000000..340b2fadfacf --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_register_token_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").to_vec(), + hex!("4a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f").to_vec(), + ], vec![ + hex!("f851a09c01dd6d2d8de951c45af23d3ad00829ce021c04d6c8acbe1612d456ee320d4980808080808080a04a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f8080808080808080").to_vec(), + hex!("f9028c30b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 393, + proposer_index: 4, + parent_root: hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), + state_root: hex!("b62ac34a8cb82497be9542fe2114410c9f6021855b766015406101a1f3d86434").into(), + body_root: hex!("04005fe231e11a5b7b1580cb73b177ae8b338bedd745497e6bb7122126a806db").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), + hex!("fa84cc88ca53a72181599ff4eb07d8b444bce023fe2347c3b4f51004c43439d3").into(), + hex!("cadc8ae211c6f2221c9138e829249adf902419c78eb4727a150baa4d9a02cc9d").into(), + hex!("33a89962df08a35c52bd7e1d887cd71fa7803e68787d05c714036f6edf75947c").into(), + hex!("2c9760fce5c2829ef3f25595a703c21eb22d0186ce223295556ed5da663a82cf").into(), + hex!("e1aa87654db79c8a0ecd6c89726bb662fcb1684badaef5cd5256f479e3c622e1").into(), + hex!("aa70d5f314e4a1fbb9c362f3db79b21bf68b328887248651fbd29fc501d0ca97").into(), + hex!("160b6c235b3a1ed4ef5f80b03ee1c76f7bf3f591c92fca9d8663e9221b9f9f0f").into(), + hex!("f68d7dcd6a07a18e9de7b5d2aa1980eb962e11d7dcb584c96e81a7635c8d2535").into(), + hex!("1d5f912dfd6697110dd1ecb5cb8e77952eef57d85deb373572572df62bb157fc").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("751414cd97c0624f922b3e80285e9f776b08fa22fd5f87391f2ed7ef571a8d46").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("8092290aa21b7751576440f77edd02a94058429ce50e63a92d620951fb25eda2").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("96a83e9ddf745346fafcb0b03d57314623df669ed543c110662b21302a0fae8b").into(), + receipts_root: hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").into(), + logs_bloom: hex!("00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000400000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000080000000000000000000000000000040004000000000000002002002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000080000000000000000000000000000000000100000000000000000200000200000010").into(), + prev_randao: hex!("62e309d4f5119d1f5c783abc20fc1a549efbab546d8d0b25ff1cfd58be524e67").into(), + block_number: 393, + gas_limit: 54492273, + gas_used: 199644, + timestamp: 1710552813, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("6a9810efb9581d30c1a5c9074f27c68ea779a8c1ae31c213241df16225f4e131").into(), + transactions_root: hex!("2cfa6ed7327e8807c7973516c5c32a68ef2459e586e8067e113d081c3bd8c07d").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("a6833fa629f3286b6916c6e50b8bf089fc9126bee6f64d0413b4e59c1265834d").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("d3af7c05c516726be7505239e0b9c7cb53d24abce6b91cdb3b3995f0164a75da").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 864, + proposer_index: 4, + parent_root: hex!("614e7672f991ac268cd841055973f55e1e42228831a211adef207bb7329be614").into(), + state_root: hex!("5fa8dfca3d760e4242ab46d529144627aa85348a19173b6e081172c701197a4a").into(), + body_root: hex!("0f34c083b1803666bb1ac5e73fa71582731a2cf37d279ff0a3b0cad5a2ff371e").into(), + }, + block_roots_root: hex!("b9aab9c388c4e4fcd899b71f62c498fc73406e38e8eb14aa440e9affa06f2a10").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs new file mode 100755 index 000000000000..4075febab59d --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_token_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26f").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").to_vec(), + ], vec![ + hex!("f90451822080b9044b02f90447018301bcb6b9010000800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000f9033cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26fb8c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 2321, + proposer_index: 5, + parent_root: hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), + state_root: hex!("d962981467920bb2b7efa4a7a1baf64745582c3250857f49a957c5dae9a0da39").into(), + body_root: hex!("18e3f7f51a350f371ad35d166f2683b42af51d1836b295e4093be08acb0dcb7a").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), + hex!("48b2e2f5256906a564e5058698f70e3406765fefd6a2edc064bb5fb88aa2ed0a").into(), + hex!("e5ed7c704e845418219b2fda42cd2f3438ffbe4c4b320935ae49439c6189f7a7").into(), + hex!("4a7ce24526b3f571548ad69679e4e260653a1b3b911a344e7f988f25a5c917a7").into(), + hex!("46fc859727ab0d0e8c344011f7d7a4426ccb537bb51363397e56cc7153f56391").into(), + hex!("f496b6f85a7c6c28a9048f2153550a7c5bcb4b23844ed3b87f6baa646124d8a3").into(), + hex!("7318644e474beb46e595a1875acc7444b937f5208065241911d2a71ac50c2de3").into(), + hex!("5cf48519e518ac64286aef5391319782dd38831d5dcc960578a6b9746d5f8cee").into(), + hex!("efb3e50fa39ca9fe7f76adbfa36fa8451ec2fd5d07b22aaf822137c04cf95a76").into(), + hex!("2206cd50750355ffaef4a67634c21168f2b564c58ffd04f33b0dc7af7dab3291").into(), + hex!("1a4014f6c4fcce9949fba74cb0f9e88df086706f9e05560cc9f0926f8c90e373").into(), + hex!("2df7cc0bcf3060be4132c63da7599c2600d9bbadf37ab001f15629bc2255698e").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("f869dd1c9598043008a3ac2a5d91b3d6c7b0bb3295b3843bc84c083d70b0e604").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("5d7859883dde1eba6c98b20eac18426134b25da2a89e5e360f3343b15e0e0a31").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("f8fbebed4c84d46231bd293bb9fbc9340d5c28c284d99fdaddb77238b8960ae2").into(), + receipts_root: hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").into(), + logs_bloom: hex!("00800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000").into(), + prev_randao: hex!("15533eeb366c6386bea5aeb8f425871928348c092209e4377f2418a6dedd7fd0").into(), + block_number: 2321, + gas_limit: 30000000, + gas_used: 113846, + timestamp: 1710554741, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("585a07122a30339b03b6481eae67c2d3de2b6b64f9f426230986519bf0f1bdfe").into(), + transactions_root: hex!("09cd60ee2207d804397c81f7b7e1e5d3307712b136e5376623a80317a4bdcd7a").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("9d419471a9a4719b40e7607781fbe32d9a7766b79805505c78c0c58133496ba2").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("bee375b8f1bbe4cd0e783c78026c1829ae72741c2dead5cab05d6834c5e5df65").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 4032, + proposer_index: 5, + parent_root: hex!("180aaaec59d38c3860e8af203f01f41c9bc41665f4d17916567c80f6cd23e8a2").into(), + state_root: hex!("3341790429ed3bf894cafa3004351d0b99e08baf6c38eb2a54d58e69fd2d19c6").into(), + body_root: hex!("a221e0c695ac7b7d04ce39b28b954d8a682ecd57961d81b44783527c6295f455").into(), + }, + block_roots_root: hex!("5744385ef06f82e67606f49aa29cd162f2e837a68fb7bd82f1fc6155d9f8640f").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs new file mode 100755 index 000000000000..6a951b568ae5 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_token_to_penpal_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aa").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").to_vec(), + ], vec![ + hex!("f90471822080b9046b02f904670183017d9cb9010000800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000f9035cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000007d0b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000201cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cf9015c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aab8e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 4235, + proposer_index: 4, + parent_root: hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), + state_root: hex!("725f51771a0ecf72c647a283ab814ca088f998eb8c203181496b0b8e01f624fa").into(), + body_root: hex!("6f1c326d192e7e97e21e27b16fd7f000b8fa09b435ff028849927e382302b0ce").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), + hex!("335eb186c077fa7053ec96dcc5d34502c997713d2d5bc4eb74842118d8cd5a64").into(), + hex!("326607faf2a7dfc9cfc4b6895f8f3d92a659552deb2c8fd1e892ec00c86c734c").into(), + hex!("4e20002125d7b6504df7c774f3f48e018e1e6762d03489149670a8335bba1425").into(), + hex!("e76af5cd61aade5aec8282b6f1df9046efa756b0466bba5e49032410f7739a1b").into(), + hex!("ee4dcd9527712116380cddafd120484a3bedf867225bbb86850b84decf6da730").into(), + hex!("e4687a07421d3150439a2cd2f09f3b468145d75b359a2e5fa88dfbec51725b15").into(), + hex!("38eaa78978e95759aa9b6f8504a8dbe36151f20ae41907e6a1ea165700ceefcd").into(), + hex!("1c1b071ec6f13e15c47d07d1bfbcc9135d6a6c819e68e7e6078a2007418c1a23").into(), + hex!("0b3ad7ad193c691c8c4ba1606ad2a90482cd1d033c7db58cfe739d0e20431e9e").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b2ffec5f2c14640305dd941330f09216c53b99d198e93735a400a6d3a4de191f").into(), + ], + finalized_block_root: hex!("08be7a59e947f08cd95c4ef470758730bf9e3b0db0824cb663ea541c39b0e65c").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("5d1186ae041f58785edb2f01248e95832f2e5e5d6c4eb8f7ff2f58980bfc2de9").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("2a66114d20e93082c8e9b47c8d401a937013487d757c9c2f3123cf43dc1f656d").into(), + receipts_root: hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").into(), + logs_bloom: hex!("00800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000").into(), + prev_randao: hex!("92e063c7e369b74149fdd1d7132ed2f635a19b9d8bff57637b8ee4736576426e").into(), + block_number: 4235, + gas_limit: 30000000, + gas_used: 97692, + timestamp: 1710556655, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("ce24fe3047aa20a8f222cd1d04567c12b39455400d681141962c2130e690953f").into(), + transactions_root: hex!("0c8388731de94771777c60d452077065354d90d6e5088db61fc6a134684195cc").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("99d397fa180078e66cd3a3b77bcb07553052f4e21d447167f3a406f663b14e6a").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("53ddf17147819c1abb918178b0230d965d1bc2c0d389f45e91e54cb1d2d468aa").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 4672, + proposer_index: 4, + parent_root: hex!("951233bf9f4bddfb2fa8f54e3bd0c7883779ef850e13e076baae3130dd7732db").into(), + state_root: hex!("4d303003b8cb097cbcc14b0f551ee70dac42de2c1cc2f4acfca7058ca9713291").into(), + body_root: hex!("664d13952b6f369bf4cf3af74d067ec33616eb57ed3a8a403fd5bae4fbf737dd").into(), + }, + block_roots_root: hex!("af71048297c070e6539cf3b9b90ae07d86d363454606bc239734629e6b49b983").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs new file mode 100644 index 000000000000..52461a8a7fbe --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use crate::Pallet as InboundQueue; +use frame_benchmarking::v2::*; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use snowbridge_pallet_inbound_queue_fixtures_v2::register_token::make_register_token_message; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + + let create_message = make_register_token_message(); + + T::Helper::initialize_storage( + create_message.finalized_header, + create_message.block_roots_root, + ); + + let sovereign_account = sibling_sovereign_account::(1000u32.into()); + + let minimum_balance = T::Token::minimum_balance(); + + // So that the receiving account exists + assert_ok!(T::Token::mint_into(&caller, minimum_balance)); + // Fund the sovereign account (parachain sovereign account) so it can transfer a reward + // fee to the caller account + assert_ok!(T::Token::mint_into( + &sovereign_account, + 3_000_000_000_000u128 + .try_into() + .unwrap_or_else(|_| panic!("unable to cast sovereign account balance")), + )); + + #[block] + { + assert_ok!(InboundQueue::::submit( + RawOrigin::Signed(caller.clone()).into(), + create_message.message, + )); + } + + Ok(()) + } + + impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs new file mode 100644 index 000000000000..31a8992442d8 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::{inbound::Log, ChannelId}; + +use sp_core::{RuntimeDebug, H160, H256}; +use sp_std::prelude::*; + +use alloy_primitives::B256; +use alloy_sol_types::{sol, SolEvent}; + +sol! { + event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); +} + +/// An inbound message that has had its outer envelope decoded. +#[derive(Clone, RuntimeDebug)] +pub struct Envelope { + /// The address of the outbound queue on Ethereum that emitted this message as an event log + pub gateway: H160, + /// The message Channel + pub channel_id: ChannelId, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// An id for tracing the message on its route (has no role in bridge consensus) + pub message_id: H256, + /// The inner payload generated from the source application. + pub payload: Vec, +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct EnvelopeDecodeError; + +impl TryFrom<&Log> for Envelope { + type Error = EnvelopeDecodeError; + + fn try_from(log: &Log) -> Result { + let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + + let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; + + Ok(Self { + gateway: log.address, + channel_id: ChannelId::from(event.channel_id.as_ref()), + nonce: event.nonce, + message_id: H256::from(event.message_id.as_ref()), + payload: event.payload, + }) + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs new file mode 100644 index 000000000000..c26859dcf5d7 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Inbound Queue +//! +//! # Overview +//! +//! Receives messages emitted by the Gateway contract on Ethereum, whereupon they are verified, +//! translated to XCM, and finally sent to their final destination parachain. +//! +//! The message relayers are rewarded using native currency from the sovereign account of the +//! destination parachain. +//! +//! # Extrinsics +//! +//! ## Governance +//! +//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable +//! processing of inbound messages. +//! +//! ## Message Submission +//! +//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination +//! parachain. +#![cfg_attr(not(feature = "std"), no_std)] + +mod envelope; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +use codec::{Decode, DecodeAll, Encode}; +use envelope::Envelope; +use frame_support::{ + traits::{ + fungible::{Inspect, Mutate}, + tokens::{Fortitude, Preservation}, + }, + weights::WeightToFee, + PalletError, +}; +use frame_system::ensure_signed; +use scale_info::TypeInfo; +use sp_core::H160; +use sp_runtime::traits::Zero; +use sp_std::vec; +use xcm::prelude::{ + send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, +}; +use xcm_executor::traits::TransactAsset; + +use snowbridge_core::{ + inbound::{Message, VerificationError, Verifier}, + sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, + StaticLookup, +}; +use snowbridge_router_primitives::inbound::v2::{ + ConvertMessage, ConvertMessageError, VersionedMessage, +}; +use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; + +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_beacon_primitives::BeaconHeader; + +type BalanceOf = + <::Token as Inspect<::AccountId>>::Balance; + +pub use pallet::*; + +pub const LOG_TARGET: &str = "snowbridge-inbound-queue"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_core::H256; + + #[pallet::pallet] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256); + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The verifier for inbound messages from Ethereum + type Verifier: Verifier; + + /// Message relayers are rewarded with this asset + type Token: Mutate + Inspect; + + /// XCM message sender + type XcmSender: SendXcm; + + // Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + + /// Convert inbound message to XCM + type MessageConverter: ConvertMessage< + AccountId = Self::AccountId, + Balance = BalanceOf, + >; + + /// Lookup a channel descriptor + type ChannelLookup: StaticLookup; + + /// Lookup pricing parameters + type PricingParameters: Get>>; + + type WeightInfo: WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + + /// Convert a weight value into deductible balance type. + type WeightToFee: WeightToFee>; + + /// Convert a length value into deductible balance type + type LengthToFee: WeightToFee>; + + /// The upper limit here only used to estimate delivery cost + type MaxMessageSize: Get; + + /// To withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A message was received from Ethereum + MessageReceived { + /// The message channel + channel_id: ChannelId, + /// The message nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + /// Fee burned for the teleport + fee_burned: BalanceOf, + }, + /// Set OperatingMode + OperatingModeChanged { mode: BasicOperatingMode }, + } + + #[pallet::error] + pub enum Error { + /// Message came from an invalid outbound channel on the Ethereum side. + InvalidGateway, + /// Message has an invalid envelope. + InvalidEnvelope, + /// Message has an unexpected nonce. + InvalidNonce, + /// Message has an invalid payload. + InvalidPayload, + /// Message channel is invalid + InvalidChannel, + /// The max nonce for the type has been reached + MaxNonceReached, + /// Cannot convert location + InvalidAccountConversion, + /// Pallet is halted + Halted, + /// Message verification error, + Verification(VerificationError), + /// XCMP send failure + Send(SendError), + /// Message conversion error + ConvertMessage(ConvertMessageError), + } + + #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] + pub enum SendError { + NotApplicable, + NotRoutable, + Transport, + DestinationUnsupported, + ExceedsMaxMessageSize, + MissingArgument, + Fees, + } + + impl From for Error { + fn from(e: XcmpSendError) -> Self { + match e { + XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), + XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), + XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), + XcmpSendError::DestinationUnsupported => + Error::::Send(SendError::DestinationUnsupported), + XcmpSendError::ExceedsMaxMessageSize => + Error::::Send(SendError::ExceedsMaxMessageSize), + XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), + XcmpSendError::Fees => Error::::Send(SendError::Fees), + } + } + } + + /// The current nonce for each channel + #[pallet::storage] + pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Submit an inbound message originating from the Gateway contract on Ethereum + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::submit())] + pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + + // submit message to verifier for verification + T::Verifier::verify(&message.event_log, &message.proof) + .map_err(|e| Error::::Verification(e))?; + + // Decode event log into an Envelope + let envelope = + Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + + // Verify that the message was submitted from the known Gateway contract + ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); + + // Retrieve the registered channel for this message + let channel = + T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::::InvalidChannel)?; + + // Verify message nonce + >::try_mutate(envelope.channel_id, |nonce| -> DispatchResult { + if *nonce == u64::MAX { + return Err(Error::::MaxNonceReached.into()) + } + if envelope.nonce != nonce.saturating_add(1) { + Err(Error::::InvalidNonce.into()) + } else { + *nonce = nonce.saturating_add(1); + Ok(()) + } + })?; + + // Reward relayer from the sovereign account of the destination parachain, only if funds + // are available + let sovereign_account = sibling_sovereign_account::(channel.para_id); + let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32); + let amount = T::Token::reducible_balance( + &sovereign_account, + Preservation::Preserve, + Fortitude::Polite, + ) + .min(delivery_cost); + if !amount.is_zero() { + T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; + } + + // Decode payload into `VersionedMessage` + let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + + // Decode message into XCM + let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?; + + log::info!( + target: LOG_TARGET, + "💫 xcm decoded as {:?} with fee {:?}", + xcm, + fee + ); + + // Burning fees for teleport + Self::burn_fees(channel.para_id, fee)?; + + // Attempt to send XCM to a dest parachain + let message_id = Self::send_xcm(xcm, channel.para_id)?; + + Self::deposit_event(Event::MessageReceived { + channel_id: envelope.channel_id, + nonce: envelope.nonce, + message_id, + fee_burned: fee, + }); + + Ok(()) + } + + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::set(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } + + impl Pallet { + pub fn do_convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, BalanceOf), Error> { + let (xcm, fee) = T::MessageConverter::convert(message_id, message) + .map_err(|e| Error::::ConvertMessage(e))?; + Ok((xcm, fee)) + } + + pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result> { + let dest = Location::new(1, [Parachain(dest.into())]); + let (xcm_hash, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Ok(xcm_hash) + } + + pub fn calculate_delivery_cost(length: u32) -> BalanceOf { + let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); + let len_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)); + weight_fee + .saturating_add(len_fee) + .saturating_add(T::PricingParameters::get().rewards.local) + } + + /// Burn the amount of the fee embedded into the XCM for teleports + pub fn burn_fees(para_id: ParaId, fee: BalanceOf) -> DispatchResult { + let dummy_context = + XcmContext { origin: None, message_id: Default::default(), topic: None }; + let dest = Location::new(1, [Parachain(para_id.into())]); + let fees = (Location::parent(), fee.saturated_into::()).into(); + T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset check out failed with error {:?}", error + ); + TokenError::FundsUnavailable + })?; + T::AssetTransactor::check_out(&dest, &fees, &dummy_context); + T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset withdraw failed with error {:?}", error + ); + TokenError::FundsUnavailable + })?; + Ok(()) + } + } + + /// API for accessing the delivery cost of a message + impl Get> for Pallet { + fn get() -> BalanceOf { + // Cost here based on MaxMessagePayloadSize(the worst case) + Self::calculate_delivery_cost(T::MaxMessageSize::get()) + } + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs new file mode 100644 index 000000000000..07e0a5564e09 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee}; +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::{ + gwei, + inbound::{Log, Proof, VerificationError}, + meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, +}; +use snowbridge_router_primitives::inbound::v2::MessageToXcm; +use sp_core::{H160, H256}; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, + BuildStorage, FixedU128, MultiSignature, +}; +use sp_std::{convert::From, default::Default}; +use xcm::prelude::*; +use xcm_executor::AssetsInHolding; + +use crate::{self as inbound_queue}; + +use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + EthereumBeaconClient: snowbridge_pallet_ethereum_client::{Pallet, Call, Storage, Event}, + InboundQueue: inbound_queue::{Pallet, Call, Storage, Event}, + } +); + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +type Balance = u128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; + type Block = Block; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions{ + genesis: Fork { + version: [0, 0, 0, 1], // 0x00000001 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 1], // 0x01000001 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 1], // 0x02000001 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 1], // 0x03000001 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 1], // 0x04000001 + epoch: 4294967295, + } + }; +} + +impl snowbridge_pallet_ethereum_client::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<32>; + type WeightInfo = (); +} + +// Mock verifier +pub struct MockVerifier; + +impl Verifier for MockVerifier { + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } +} + +const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; + +parameter_types! { + pub const EthereumNetwork: xcm::v3::NetworkId = xcm::v3::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetExecutionFee: u128 = 2_000_000_000; + pub const CreateAssetDeposit: u128 = 100_000_000_000; + pub const SendTokenExecutionFee: u128 = 1_000_000_000; + pub const InitialFund: u128 = 1_000_000_000_000; + pub const InboundQueuePalletInstance: u8 = 80; + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for Test { + // not implemented since the MockVerifier is used for tests + fn initialize_storage(_: BeaconHeader, _: H256) {} +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; + +impl SendXcm for MockXcmSender { + type Ticket = Xcm<()>; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::default())) + } + } + + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } +} + +parameter_types! { + pub const OwnParaId: ParaId = ParaId::new(1013); + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: DOT, remote: meth(1) }, + multiplier: FixedU128::from_rational(1, 1), + }; +} + +pub const DOT: u128 = 10_000_000_000; + +pub struct MockChannelLookup; +impl StaticLookup for MockChannelLookup { + type Source = ChannelId; + type Target = Channel; + + fn lookup(channel_id: Self::Source) -> Option { + if channel_id != + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into() + { + return None + } + Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() }) + } +} + +pub struct SuccessfulTransactor; +impl TransactAsset for SuccessfulTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +impl inbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type Token = Balances; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + MockTokenIdConvert, + UniversalLocation, + AssetHubFromEthereum, + >; + type PricingParameters = Parameters; + type ChannelLookup = MockChannelLookup; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + type MaxMessageSize = ConstU32<1024>; + type AssetTransactor = SuccessfulTransactor; +} + +pub fn last_events(n: usize) -> Vec { + frame_system::Pallet::::events() + .into_iter() + .rev() + .take(n) + .rev() + .map(|e| e.event) + .collect() +} + +pub fn expect_events(e: Vec) { + assert_eq!(last_events(e.len()), e); +} + +pub fn setup() { + System::set_block_number(1); + Balances::mint_into( + &sibling_sovereign_account::(ASSET_HUB_PARAID.into()), + InitialFund::get(), + ) + .unwrap(); + Balances::mint_into( + &sibling_sovereign_account::(TEMPLATE_PARAID.into()), + InitialFund::get(), + ) + .unwrap(); +} + +pub fn new_tester() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext +} + +// Generated from smoketests: +// cd smoketests +// ./make-bindings +// cargo test --test register_token -- --nocapture +pub fn mock_event_log() -> Log { + Log { + // gateway address + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + } +} + +pub fn mock_event_log_invalid_channel() -> Log { + Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // invalid channel id + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub fn mock_event_log_invalid_gateway() -> Log { + Log { + // gateway address + address: H160::zero(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub fn mock_execution_proof() -> ExecutionProof { + ExecutionProof { + header: BeaconHeader::default(), + ancestry_proof: None, + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: Default::default(), + fee_recipient: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: vec![], + prev_randao: Default::default(), + block_number: 0, + gas_limit: 0, + gas_used: 0, + timestamp: 0, + extra_data: vec![], + base_fee_per_gas: Default::default(), + block_hash: Default::default(), + transactions_root: Default::default(), + withdrawals_root: Default::default(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![], + } +} + +pub const ASSET_HUB_PARAID: u32 = 1000u32; +pub const TEMPLATE_PARAID: u32 = 1001u32; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs new file mode 100644 index 000000000000..44f6c0ebc658 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{assert_noop, assert_ok}; +use hex_literal::hex; +use snowbridge_core::{inbound::Proof, ChannelId}; +use sp_keyring::AccountKeyring as Keyring; +use sp_runtime::DispatchError; +use sp_std::convert::From; + +use crate::{Error, Event as InboundQueueEvent}; + +use crate::mock::*; + +#[test] +fn test_submit_happy_path() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let channel_sovereign = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + + let origin = RuntimeOrigin::signed(relayer.clone()); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + let initial_fund = InitialFund::get(); + assert_eq!(Balances::balance(&relayer), 0); + assert_eq!(Balances::balance(&channel_sovereign), initial_fund); + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") + .into(), + nonce: 1, + message_id: [ + 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, + 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, + ], + fee_burned: 110000000000, + } + .into()]); + + let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); + assert!( + Parameters::get().rewards.local < delivery_cost, + "delivery cost exceeds pure reward" + ); + + assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded"); + assert!( + Balances::balance(&channel_sovereign) <= initial_fund - delivery_cost, + "sovereign account paid reward" + ); + }); +} + +#[test] +fn test_submit_xcm_invalid_channel() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of parachain 1001 + let sovereign_account = sibling_sovereign_account::(TEMPLATE_PARAID.into()); + println!("account: {}", sovereign_account); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_channel(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidChannel, + ); + }); +} + +#[test] +fn test_submit_with_invalid_gateway() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of Asset Hub (Statemint) + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_gateway(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidGateway + ); + }); +} + +#[test] +fn test_submit_with_invalid_nonce() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of Asset Hub (Statemint) + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + let nonce: u64 = >::get(ChannelId::from(hex!( + "c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539" + ))); + assert_eq!(nonce, 1); + + // Submit the same again + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidNonce + ); + }); +} + +#[test] +fn test_submit_no_funds_to_reward_relayers_just_ignore() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Reset balance of sovereign_account to zero first + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + Balances::set_balance(&sovereign_account, 0); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + // Check submit successfully in case no funds available + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + }); +} + +#[test] +fn test_set_operating_mode() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + assert_ok!(InboundQueue::set_operating_mode( + RuntimeOrigin::root(), + snowbridge_core::BasicOperatingMode::Halted + )); + + assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); + }); +} + +#[test] +fn test_set_operating_mode_root_only() { + new_tester().execute_with(|| { + assert_noop!( + InboundQueue::set_operating_mode( + RuntimeOrigin::signed(Keyring::Bob.into()), + snowbridge_core::BasicOperatingMode::Halted + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Reset balance of sovereign account to (ED+1) first + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + Balances::set_balance(&sovereign_account, ExistentialDeposit::get() + 1); + + // Submit message successfully + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + // Check balance of sovereign account to ED + let amount = Balances::balance(&sovereign_account); + assert_eq!(amount, ExistentialDeposit::get()); + + // Submit another message with nonce set as 2 + let mut event_log = mock_event_log(); + event_log.data[31] = 2; + let message = Message { + event_log, + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + // Check balance of sovereign account as ED does not change + let amount = Balances::balance(&sovereign_account); + assert_eq!(amount, ExistentialDeposit::get()); + }); +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs new file mode 100644 index 000000000000..c2c665f40d9e --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Autogenerated weights for `snowbridge_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +#![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 ethereum_beacon_client. +pub trait WeightInfo { + fn submit() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn submit() -> Weight { + Weight::from_parts(70_000_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index 423b92b9fae0..5814886fe355 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -61,7 +61,7 @@ use snowbridge_core::{ sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, StaticLookup, }; -use snowbridge_router_primitives::inbound::{ +use snowbridge_router_primitives::inbound::v1::{ ConvertMessage, ConvertMessageError, VersionedMessage, }; use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 675d4b691593..82862616466d 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -12,7 +12,7 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, }; -use snowbridge_router_primitives::inbound::MessageToXcm; +use snowbridge_router_primitives::inbound::v1::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml new file mode 100644 index 000000000000..560192c759f8 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml @@ -0,0 +1,92 @@ +[package] +name = "snowbridge-pallet-outbound-queue-v2" +description = "Snowbridge Outbound Queue Pallet V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { features = ["alloc", "derive"], workspace = true } +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +alloy-primitives = { features = ["rlp"], workspace = true } +alloy-sol-types = { workspace = true } + +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-runtime = { workspace = true } +sp-io = { workspace = true } +sp-arithmetic = { workspace = true } + +bridge-hub-common = { workspace = true } + +snowbridge-core = { features = ["serde"], workspace = true } +ethabi = { workspace = true } +hex-literal = { workspace = true, default-features = true } +snowbridge-merkle-tree = { workspace = true } +snowbridge-router-primitives = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-builder = { workspace = true } + +[dev-dependencies] +pallet-message-queue = { workspace = true } +sp-keyring = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-sol-types/std", + "bridge-hub-common/std", + "codec/std", + "ethabi/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-message-queue/std", + "scale-info/std", + "serde/std", + "snowbridge-core/std", + "snowbridge-merkle-tree/std", + "snowbridge-router-primitives/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "bridge-hub-common/runtime-benchmarks", + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-message-queue/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/README.md b/bridges/snowbridge/pallets/outbound-queue-v2/README.md new file mode 100644 index 000000000000..19638f90e6a5 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/README.md @@ -0,0 +1,3 @@ +# Ethereum Outbound Queue + +Sends messages from an origin in the Polkadot ecosystem to Ethereum. diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml new file mode 100644 index 000000000000..14f4a8d18c19 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "snowbridge-outbound-queue-runtime-api-v2" +description = "Snowbridge Outbound Queue Runtime API V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-std = { workspace = true } +sp-api = { workspace = true } +frame-support = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-merkle-tree = { workspace = true } +xcm = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "scale-info/std", + "snowbridge-core/std", + "snowbridge-merkle-tree/std", + "sp-api/std", + "sp-std/std", + "xcm/std", +] diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md new file mode 100644 index 000000000000..98ae01fb33da --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md @@ -0,0 +1,6 @@ +# Ethereum Outbound Queue Runtime API + +Provides an API: + +- to generate merkle proofs for outbound messages +- calculate delivery fee for delivering messages to Ethereum diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs new file mode 100644 index 000000000000..26ab7872ff11 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::tokens::Balance as BalanceT; +use snowbridge_core::outbound::{ + v2::{Fee, InboundMessage}, + DryRunError, +}; +use snowbridge_merkle_tree::MerkleProof; +use xcm::prelude::Xcm; + +sp_api::decl_runtime_apis! { + pub trait OutboundQueueApiV2 where Balance: BalanceT + { + /// Generate a merkle proof for a committed message identified by `leaf_index`. + /// The merkle root is stored in the block header as a + /// `sp_runtime::generic::DigestItem::Other` + fn prove_message(leaf_index: u64) -> Option; + + fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Fee),DryRunError>; + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs new file mode 100644 index 000000000000..f45e15bad647 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helpers for implementing runtime api + +use crate::{Config, MessageLeaves}; +use frame_support::storage::StorageStreamIter; +use snowbridge_core::{ + outbound::{ + v2::{CommandWrapper, Fee, GasMeter, InboundMessage, Message}, + DryRunError, + }, + AgentIdOf, +}; +use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; +use snowbridge_router_primitives::outbound::v2::XcmConverter; +use sp_core::Get; +use sp_std::{default::Default, vec::Vec}; +use xcm::{ + latest::Location, + prelude::{Parachain, Xcm}, +}; +use xcm_executor::traits::ConvertLocation; + +pub fn prove_message(leaf_index: u64) -> Option +where + T: Config, +{ + if !MessageLeaves::::exists() { + return None + } + let proof = + merkle_proof::<::Hashing, _>(MessageLeaves::::stream_iter(), leaf_index); + Some(proof) +} + +pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, Fee), DryRunError> +where + T: Config, +{ + let mut converter = XcmConverter::::new( + &xcm, + T::EthereumNetwork::get(), + AgentIdOf::convert_location(&Location::new(1, Parachain(1000))) + .ok_or(DryRunError::ConvertLocationFailed)?, + ); + + let message: Message = converter.convert().map_err(|_| DryRunError::ConvertXcmFailed)?; + + let fee = Fee::from(crate::Pallet::::calculate_local_fee()); + + let commands: Vec = message + .commands + .into_iter() + .map(|command| CommandWrapper { + kind: command.index(), + gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), + payload: command.abi_encode(), + }) + .collect(); + + let committed_message = InboundMessage { + origin: message.origin, + nonce: Default::default(), + commands: commands.try_into().map_err(|_| DryRunError::ConvertXcmFailed)?, + }; + + Ok((committed_message, fee)) +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs new file mode 100644 index 000000000000..f6e02844a58d --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use bridge_hub_common::AggregateMessageOrigin; +use codec::Encode; +use frame_benchmarking::v2::*; +use snowbridge_core::{ + outbound::v1::{Command, Initializer, QueuedMessage}, + ChannelId, +}; +use sp_core::{H160, H256}; + +#[allow(unused_imports)] +use crate::Pallet as OutboundQueue; + +#[benchmarks( + where + ::MaxMessagePayloadSize: Get, +)] +mod benchmarks { + use super::*; + + /// Benchmark for processing a message. + #[benchmark] + fn do_process_message() -> Result<(), BenchmarkError> { + let enqueued_message = QueuedMessage { + id: H256::zero(), + channel_id: ChannelId::from([1; 32]), + command: Command::Upgrade { + impl_address: H160::zero(), + impl_code_hash: H256::zero(), + initializer: Some(Initializer { + params: [7u8; 256].into_iter().collect(), + maximum_required_gas: 200_000, + }), + }, + }; + let origin = AggregateMessageOrigin::Snowbridge([1; 32].into()); + let encoded_enqueued_message = enqueued_message.encode(); + + #[block] + { + let _ = OutboundQueue::::do_process_message(origin, &encoded_enqueued_message); + } + + assert_eq!(MessageLeaves::::decode_len().unwrap(), 1); + + Ok(()) + } + + /// Benchmark for producing final messages commitment + #[benchmark] + fn commit() -> Result<(), BenchmarkError> { + // Assume worst case, where `MaxMessagesPerBlock` messages need to be committed. + for i in 0..T::MaxMessagesPerBlock::get() { + let leaf_data: [u8; 1] = [i as u8]; + let leaf = ::Hashing::hash(&leaf_data); + MessageLeaves::::append(leaf); + } + + #[block] + { + OutboundQueue::::commit(); + } + + Ok(()) + } + + /// Benchmark for producing commitment for a single message + #[benchmark] + fn commit_single() -> Result<(), BenchmarkError> { + let leaf = ::Hashing::hash(&[100; 1]); + MessageLeaves::::append(leaf); + + #[block] + { + OutboundQueue::::commit(); + } + + Ok(()) + } + + impl_benchmark_test_suite!(OutboundQueue, crate::mock::new_tester(), crate::mock::Test,); +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs new file mode 100644 index 000000000000..e0f6ba63291c --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::inbound::Log; + +use sp_core::{RuntimeDebug, H160}; +use sp_std::prelude::*; + +use alloy_primitives::B256; +use alloy_sol_types::{sol, SolEvent}; + +sol! { + event InboundMessageDispatched(uint64 indexed nonce, bool success, bytes32 indexed reward_address); +} + +/// An inbound message that has had its outer envelope decoded. +#[derive(Clone, RuntimeDebug)] +pub struct Envelope { + /// The address of the outbound queue on Ethereum that emitted this message as an event log + pub gateway: H160, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// Delivery status + pub success: bool, + /// The reward address + pub reward_address: [u8; 32], +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct EnvelopeDecodeError; + +impl TryFrom<&Log> for Envelope { + type Error = EnvelopeDecodeError; + + fn try_from(log: &Log) -> Result { + let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + + let event = InboundMessageDispatched::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; + + Ok(Self { + gateway: log.address, + nonce: event.nonce, + success: event.success, + reward_address: event.reward_address.clone().into(), + }) + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs new file mode 100644 index 000000000000..43fde9528f5d --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Pallet for committing outbound messages for delivery to Ethereum +//! +//! # Overview +//! +//! Messages come either from sibling parachains via XCM, or BridgeHub itself +//! via the `snowbridge-pallet-system`: +//! +//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver` +//! 2. `snowbridge_pallet_system::Pallet::send` +//! +//! The message submission pipeline works like this: +//! 1. The message is first validated via the implementation for +//! [`snowbridge_core::outbound::SendMessage::validate`] +//! 2. The message is then enqueued for later processing via the implementation for +//! [`snowbridge_core::outbound::SendMessage::deliver`] +//! 3. The underlying message queue is implemented by [`Config::MessageQueue`] +//! 4. The message queue delivers messages back to this pallet via the implementation for +//! [`frame_support::traits::ProcessMessage::process_message`] +//! 5. The message is processed in `Pallet::do_process_message`: a. Assigned a nonce b. ABI-encoded, +//! hashed, and stored in the `MessageLeaves` vector +//! 6. At the end of the block, a merkle root is constructed from all the leaves in `MessageLeaves`. +//! 7. This merkle root is inserted into the parachain header as a digest item +//! 8. Offchain relayers are able to relay the message to Ethereum after: a. Generating a merkle +//! proof for the committed message using the `prove_message` runtime API b. Reading the actual +//! message content from the `Messages` vector in storage +//! +//! On the Ethereum side, the message root is ultimately the thing being +//! verified by the Polkadot light client. +//! +//! # Message Priorities +//! +//! The processing of governance commands can never be halted. This effectively +//! allows us to pause processing of normal user messages while still allowing +//! governance commands to be sent to Ethereum. +//! +//! # Fees +//! +//! An upfront fee must be paid for delivering a message. This fee covers several +//! components: +//! 1. The weight of processing the message locally +//! 2. The gas refund paid out to relayers for message submission +//! 3. An additional reward paid out to relayers for message submission +//! +//! Messages are weighed to determine the maximum amount of gas they could +//! consume on Ethereum. Using this upper bound, a final fee can be calculated. +//! +//! The fee calculation also requires the following parameters: +//! * Average ETH/DOT exchange rate over some period +//! * Max fee per unit of gas that bridge is willing to refund relayers for +//! +//! By design, it is expected that governance should manually update these +//! parameters every few weeks using the `set_pricing_parameters` extrinsic in the +//! system pallet. +//! +//! This is an interim measure. Once ETH/DOT liquidity pools are available in the Polkadot network, +//! we'll use them as a source of pricing info, subject to certain safeguards. +//! +//! ## Fee Computation Function +//! +//! ```text +//! LocalFee(Message) = WeightToFee(ProcessMessageWeight(Message)) +//! RemoteFee(Message) = MaxGasRequired(Message) * Params.MaxFeePerGas + Params.Reward +//! RemoteFeeAdjusted(Message) = Params.Multiplier * (RemoteFee(Message) / Params.Ratio("ETH/DOT")) +//! Fee(Message) = LocalFee(Message) + RemoteFeeAdjusted(Message) +//! ``` +//! +//! By design, the computed fee includes a safety factor (the `Multiplier`) to cover +//! unfavourable fluctuations in the ETH/DOT exchange rate. +//! +//! ## Fee Settlement +//! +//! On the remote side, in the gateway contract, the relayer accrues +//! +//! ```text +//! Min(GasPrice, Message.MaxFeePerGas) * GasUsed() + Message.Reward +//! ``` +//! Or in plain english, relayers are refunded for gas consumption, using a +//! price that is a minimum of the actual gas price, or `Message.MaxFeePerGas`. +//! +//! # Extrinsics +//! +//! * [`Call::set_operating_mode`]: Set the operating mode +//! +//! # Runtime API +//! +//! * `prove_message`: Generate a merkle proof for a committed message +//! * `calculate_fee`: Calculate the delivery fee for a message +#![cfg_attr(not(feature = "std"), no_std)] +pub mod api; +pub mod envelope; +pub mod process_message_impl; +pub mod send_message_impl; +pub mod types; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem}; +use codec::Decode; +use envelope::Envelope; +use frame_support::{ + storage::StorageStreamIter, + traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError}, + weights::{Weight, WeightToFee}, +}; +use snowbridge_core::{ + inbound::Message as DeliveryMessage, + outbound::v2::{CommandWrapper, Fee, GasMeter, InboundMessage, InboundMessageWrapper, Message}, + BasicOperatingMode, RewardLedger, TokenId, +}; +use snowbridge_merkle_tree::merkle_root; +use sp_core::H256; +use sp_runtime::{ + traits::{BlockNumberProvider, Hash}, + ArithmeticError, DigestItem, +}; +use sp_std::prelude::*; +pub use types::{PendingOrder, ProcessMessageOriginOf}; +pub use weights::WeightInfo; + +pub use pallet::*; + +use alloy_sol_types::SolValue; + +use alloy_primitives::FixedBytes; + +use sp_runtime::traits::TrailingZeroInput; + +use sp_runtime::traits::MaybeEquivalence; + +use xcm::prelude::{Location, NetworkId}; + +use snowbridge_core::inbound::{VerificationError, Verifier}; + +use sp_core::H160; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type Hashing: Hash; + + type MessageQueue: EnqueueMessage; + + /// Measures the maximum gas used to execute a command on Ethereum + type GasMeter: GasMeter; + + type Balance: Balance + From; + + /// Max bytes in a message payload + #[pallet::constant] + type MaxMessagePayloadSize: Get; + + /// Max number of messages processed per block + #[pallet::constant] + type MaxMessagesPerBlock: Get; + + /// Convert a weight value into a deductible fee based. + type WeightToFee: WeightToFee; + + /// Weight information for extrinsics in this pallet + type WeightInfo: WeightInfo; + + /// The verifier for delivery proof from Ethereum + type Verifier: Verifier; + + /// Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + + /// Reward leger + type RewardLedger: RewardLedger<::AccountId, Self::Balance>; + + type ConvertAssetId: MaybeEquivalence; + + type EthereumNetwork: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Message has been queued and will be processed in the future + MessageQueued { + /// The message + message: Message, + }, + /// Message will be committed at the end of current block. From now on, to track the + /// progress the message, use the `nonce` of `id`. + MessageAccepted { + /// ID of the message + id: H256, + /// The nonce assigned to this message + nonce: u64, + }, + /// Some messages have been committed + MessagesCommitted { + /// Merkle root of the committed messages + root: H256, + /// number of committed messages + count: u64, + }, + /// Set OperatingMode + OperatingModeChanged { mode: BasicOperatingMode }, + /// Delivery Proof received + MessageDeliveryProofReceived { nonce: u64 }, + } + + #[pallet::error] + pub enum Error { + /// The message is too large + MessageTooLarge, + /// The pallet is halted + Halted, + /// Invalid Channel + InvalidChannel, + /// Invalid Envelope + InvalidEnvelope, + /// Message verification error + Verification(VerificationError), + /// Invalid Gateway + InvalidGateway, + /// No pending nonce + PendingNonceNotExist, + } + + /// Messages to be committed in the current block. This storage value is killed in + /// `on_initialize`, so should never go into block PoV. + /// + /// Is never read in the runtime, only by offchain message relayers. + /// + /// Inspired by the `frame_system::Pallet::Events` storage value + #[pallet::storage] + #[pallet::unbounded] + pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; + + /// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a + /// merkle root during `on_finalize`. This storage value is killed in + /// `on_initialize`, so should never go into block PoV. + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn message_leaves)] + pub(super) type MessageLeaves = StorageValue<_, Vec, ValueQuery>; + + /// The current nonce for the messages + #[pallet::storage] + pub type Nonce = StorageValue<_, u64, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + /// Pending orders to relay + #[pallet::storage] + pub type PendingOrders = + StorageMap<_, Identity, u64, PendingOrder>, OptionQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet + where + T::AccountId: AsRef<[u8]>, + { + fn on_initialize(_: BlockNumberFor) -> Weight { + // Remove storage from previous block + Messages::::kill(); + MessageLeaves::::kill(); + // Reserve some weight for the `on_finalize` handler + T::WeightInfo::commit() + } + + fn on_finalize(_: BlockNumberFor) { + Self::commit(); + } + } + + #[pallet::call] + impl Pallet { + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(0)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::put(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::submit_delivery_proof())] + pub fn submit_delivery_proof( + origin: OriginFor, + message: DeliveryMessage, + ) -> DispatchResult { + ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + + // submit message to verifier for verification + T::Verifier::verify(&message.event_log, &message.proof) + .map_err(|e| Error::::Verification(e))?; + + // Decode event log into an Envelope + let envelope = + Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + + // Verify that the message was submitted from the known Gateway contract + ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); + + let nonce = envelope.nonce; + ensure!(>::contains_key(nonce), Error::::PendingNonceNotExist); + + let order = >::get(nonce).ok_or(Error::::PendingNonceNotExist)?; + let account = T::AccountId::decode(&mut &envelope.reward_address[..]).unwrap_or( + T::AccountId::decode(&mut TrailingZeroInput::zeroes()).expect("zero address"), + ); + // No fee for governance order + if !order.fee.is_zero() { + T::RewardLedger::deposit(account, order.fee.into())?; + } + + >::remove(nonce); + + Self::deposit_event(Event::MessageDeliveryProofReceived { nonce }); + + Ok(()) + } + } + + impl Pallet { + /// Generate a messages commitment and insert it into the header digest + pub(crate) fn commit() { + let count = MessageLeaves::::decode_len().unwrap_or_default() as u64; + if count == 0 { + return + } + + // Create merkle root of messages + let root = merkle_root::<::Hashing, _>(MessageLeaves::::stream_iter()); + + let digest_item: DigestItem = CustomDigestItem::Snowbridge(root).into(); + + // Insert merkle root into the header digest + >::deposit_log(digest_item); + + Self::deposit_event(Event::MessagesCommitted { root, count }); + } + + /// Process a message delivered by the MessageQueue pallet + pub(crate) fn do_process_message( + _: ProcessMessageOriginOf, + mut message: &[u8], + ) -> Result { + use ProcessMessageError::*; + + // Yield if the maximum number of messages has been processed this block. + // This ensures that the weight of `on_finalize` has a known maximum bound. + ensure!( + MessageLeaves::::decode_len().unwrap_or(0) < + T::MaxMessagesPerBlock::get() as usize, + Yield + ); + + // Decode bytes into versioned message + let message: Message = Message::decode(&mut message).map_err(|_| Corrupt)?; + + let nonce = Nonce::::get(); + + let commands: Vec = message + .commands + .into_iter() + .map(|command| CommandWrapper { + kind: command.index(), + gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), + payload: command.abi_encode(), + }) + .collect(); + + // Construct the final committed message + let inbound_message = InboundMessage { + origin: message.origin, + nonce, + commands: commands.clone().try_into().map_err(|_| Corrupt)?, + }; + + let committed_message = InboundMessageWrapper { + origin: FixedBytes::from(message.origin.as_fixed_bytes()), + nonce, + commands, + }; + + // ABI-encode and hash the prepared message + let message_abi_encoded = committed_message.abi_encode(); + let message_abi_encoded_hash = ::Hashing::hash(&message_abi_encoded); + + Messages::::append(Box::new(inbound_message)); + MessageLeaves::::append(message_abi_encoded_hash); + + >::try_mutate(nonce, |maybe_locked| -> DispatchResult { + let mut locked = maybe_locked.clone().unwrap_or_else(|| PendingOrder { + nonce, + fee: 0, + block_number: frame_system::Pallet::::current_block_number(), + }); + locked.fee = + locked.fee.checked_add(message.fee).ok_or(ArithmeticError::Overflow)?; + *maybe_locked = Some(locked); + Ok(()) + }) + .map_err(|_| Unsupported)?; + + Nonce::::set(nonce.checked_add(1).ok_or(Unsupported)?); + + Self::deposit_event(Event::MessageAccepted { id: message.id, nonce }); + + Ok(true) + } + + /// The local component of the message processing fees in native currency + pub(crate) fn calculate_local_fee() -> T::Balance { + T::WeightToFee::weight_to_fee( + &T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()), + ) + } + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs new file mode 100644 index 000000000000..353747b23a5f --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{ + derive_impl, parameter_types, + traits::{Everything, Hooks}, + weights::IdentityFee, + BoundedVec, +}; + +use hex_literal::hex; +use snowbridge_core::{ + gwei, + inbound::{Log, Proof, VerificationError, Verifier}, + meth, + outbound::v2::*, + pricing::{PricingParameters, Rewards}, + ParaId, +}; +use sp_core::{ConstU32, H160, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, Keccak256}, + AccountId32, BuildStorage, FixedU128, +}; +use sp_std::marker::PhantomData; + +type Block = frame_system::mocking::MockBlock; +type AccountId = AccountId32; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + OutboundQueue: crate::{Pallet, Storage, Event}, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type Nonce = u64; + type Block = Block; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl pallet_message_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = OutboundQueue; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; + type IdleMaxServiceWeight = (); + type QueuePausedQuery = (); +} + +// Mock verifier +pub struct MockVerifier; + +impl Verifier for MockVerifier { + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } +} + +const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; +const WETH: [u8; 20] = hex!["C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"]; +const ASSET_HUB_AGENT: [u8; 32] = + hex!["81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79"]; + +parameter_types! { + pub const OwnParaId: ParaId = ParaId::new(1013); + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: DOT, remote: meth(1) }, + multiplier: FixedU128::from_rational(4, 3), + }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; + +} + +pub const DOT: u128 = 10_000_000_000; +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type GatewayAddress = GatewayAddress; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type MaxMessagePayloadSize = ConstU32<1024>; + type MaxMessagesPerBlock = ConstU32<20>; + type GasMeter = ConstantGasMeter; + type Balance = u128; + type WeightToFee = IdentityFee; + type WeightInfo = (); + type RewardLedger = (); + type ConvertAssetId = (); + type EthereumNetwork = EthereumNetwork; +} + +fn setup() { + System::set_block_number(1); +} + +pub fn new_tester() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext +} + +pub fn run_to_end_of_next_block() { + // finish current block + MessageQueue::on_finalize(System::block_number()); + OutboundQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + // start next block + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + OutboundQueue::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()); + // finish next block + MessageQueue::on_finalize(System::block_number()); + OutboundQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); +} + +pub fn mock_governance_message() -> Message +where + T: Config, +{ + let _marker = PhantomData::; // for clippy + + Message { + origin: primary_governance_origin(), + id: Default::default(), + fee: 0, + commands: BoundedVec::try_from(vec![Command::Upgrade { + impl_address: Default::default(), + impl_code_hash: Default::default(), + initializer: None, + }]) + .unwrap(), + } +} + +// Message should fail validation as it is too large +pub fn mock_invalid_governance_message() -> Message +where + T: Config, +{ + let _marker = PhantomData::; // for clippy + + Message { + origin: Default::default(), + id: Default::default(), + fee: 0, + commands: BoundedVec::try_from(vec![Command::Upgrade { + impl_address: H160::zero(), + impl_code_hash: H256::zero(), + initializer: Some(Initializer { + params: (0..1000).map(|_| 1u8).collect::>(), + maximum_required_gas: 0, + }), + }]) + .unwrap(), + } +} + +pub fn mock_message(sibling_para_id: u32) -> Message { + Message { + origin: H256::from_low_u64_be(sibling_para_id as u64), + id: H256::from_low_u64_be(1), + fee: 1_000, + commands: BoundedVec::try_from(vec![Command::UnlockNativeToken { + agent_id: H256(ASSET_HUB_AGENT), + token: H160(WETH), + recipient: H160(GATEWAY_ADDRESS), + amount: 1_000_000, + }]) + .unwrap(), + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/process_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/process_message_impl.rs new file mode 100644 index 000000000000..731aa6fa6d5c --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/process_message_impl.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Implementation for [`frame_support::traits::ProcessMessage`] +use super::*; +use crate::weights::WeightInfo; +use frame_support::{ + traits::{ProcessMessage, ProcessMessageError}, + weights::WeightMeter, +}; + +impl ProcessMessage for Pallet { + type Origin = AggregateMessageOrigin; + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + _: &mut [u8; 32], + ) -> Result { + let weight = T::WeightInfo::do_process_message(); + if meter.try_consume(weight).is_err() { + return Err(ProcessMessageError::Overweight(weight)) + } + Self::do_process_message(origin, message) + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs new file mode 100644 index 000000000000..c37cf0dfa530 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Implementation for [`snowbridge_core::outbound::SendMessage`] +use super::*; +use bridge_hub_common::AggregateMessageOrigin; +use codec::Encode; +use frame_support::{ + ensure, + traits::{EnqueueMessage, Get}, +}; +use snowbridge_core::outbound::{ + v2::{primary_governance_origin, Message, SendMessage}, + SendError, SendMessageFeeProvider, +}; +use sp_core::H256; +use sp_runtime::BoundedVec; + +/// The maximal length of an enqueued message, as determined by the MessageQueue pallet +pub type MaxEnqueuedMessageSizeOf = + <::MessageQueue as EnqueueMessage>::MaxMessageLen; + +impl SendMessage for Pallet +where + T: Config, +{ + type Ticket = Message; + + fn validate( + message: &Message, + ) -> Result<(Self::Ticket, Fee<::Balance>), SendError> { + // The inner payload should not be too large + let payload = message.encode(); + ensure!( + payload.len() < T::MaxMessagePayloadSize::get() as usize, + SendError::MessageTooLarge + ); + + let fee = Fee::from(Self::calculate_local_fee()); + + Ok((message.clone(), fee)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + let origin = AggregateMessageOrigin::SnowbridgeV2(ticket.origin); + + if ticket.origin != primary_governance_origin() { + ensure!(!Self::operating_mode().is_halted(), SendError::Halted); + } + + let message = + BoundedVec::try_from(ticket.encode()).map_err(|_| SendError::MessageTooLarge)?; + + T::MessageQueue::enqueue_message(message.as_bounded_slice(), origin); + Self::deposit_event(Event::MessageQueued { message: ticket.clone() }); + Ok(ticket.id) + } +} + +impl SendMessageFeeProvider for Pallet { + type Balance = T::Balance; + + /// The local component of the message processing fees in native currency + fn local_fee() -> Self::Balance { + Self::calculate_local_fee() + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs new file mode 100644 index 000000000000..b4d70e37a9e4 --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -0,0 +1,273 @@ +use alloy_primitives::FixedBytes; +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{mock::*, *}; + +use frame_support::{ + assert_err, assert_noop, assert_ok, + traits::{Hooks, ProcessMessage, ProcessMessageError}, + weights::WeightMeter, + BoundedVec, +}; + +use codec::Encode; +use snowbridge_core::{ + outbound::{ + v2::{primary_governance_origin, Command, InboundMessageWrapper, SendMessage}, + SendError, + }, + ChannelId, ParaId, +}; +use sp_core::{hexdisplay::HexDisplay, H256}; + +#[test] +fn submit_messages_and_commit() { + new_tester().execute_with(|| { + for para_id in 1000..1004 { + let message = mock_message(para_id); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_ok!(OutboundQueue::deliver(ticket)); + } + + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + assert_eq!(Nonce::::get(), 4); + + let digest = System::digest(); + let digest_items = digest.logs(); + assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some()); + assert_eq!(Messages::::decode_len(), Some(4)); + }); +} + +#[test] +fn submit_message_fail_too_large() { + new_tester().execute_with(|| { + let message = mock_invalid_governance_message::(); + assert_err!(OutboundQueue::validate(&message), SendError::MessageTooLarge); + }); +} + +#[test] +fn commit_exits_early_if_no_processed_messages() { + new_tester().execute_with(|| { + // on_finalize should do nothing, nor should it panic + OutboundQueue::on_finalize(System::block_number()); + + let digest = System::digest(); + let digest_items = digest.logs(); + assert_eq!(digest_items.len(), 0); + }); +} + +#[test] +fn process_message_yields_on_max_messages_per_block() { + new_tester().execute_with(|| { + for _ in 0..::MaxMessagesPerBlock::get() { + MessageLeaves::::append(H256::zero()) + } + + let _channel_id: ChannelId = ParaId::from(1000).into(); + let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero()); + let message = Message { + origin: Default::default(), + id: Default::default(), + fee: 0, + commands: BoundedVec::try_from(vec![Command::Upgrade { + impl_address: Default::default(), + impl_code_hash: Default::default(), + initializer: None, + }]) + .unwrap(), + }; + + let mut meter = WeightMeter::new(); + + assert_noop!( + OutboundQueue::process_message( + message.encode().as_slice(), + origin, + &mut meter, + &mut [0u8; 32] + ), + ProcessMessageError::Yield + ); + }) +} + +#[test] +fn process_message_fails_on_max_nonce_reached() { + new_tester().execute_with(|| { + let sibling_id = 1000; + let _channel_id: ChannelId = ParaId::from(sibling_id).into(); + let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero()); + let message: Message = mock_message(sibling_id); + + let mut meter = WeightMeter::with_limit(Weight::MAX); + + Nonce::::set(u64::MAX); + + let result = OutboundQueue::process_message( + message.encode().as_slice(), + origin, + &mut meter, + &mut [0u8; 32], + ); + assert_err!(result, ProcessMessageError::Unsupported) + }) +} + +#[test] +fn process_message_fails_on_overweight_message() { + new_tester().execute_with(|| { + let sibling_id = 1000; + let _channel_id: ChannelId = ParaId::from(sibling_id).into(); + let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero()); + let message: Message = mock_message(sibling_id); + let mut meter = WeightMeter::with_limit(Weight::from_parts(1, 1)); + assert_noop!( + OutboundQueue::process_message( + message.encode().as_slice(), + origin, + &mut meter, + &mut [0u8; 32] + ), + ProcessMessageError::Overweight(::WeightInfo::do_process_message()) + ); + }) +} + +// Governance messages should be able to bypass a halted operating mode +// Other message sends should fail when halted +#[test] +fn submit_upgrade_message_success_when_queue_halted() { + new_tester().execute_with(|| { + // halt the outbound queue + OutboundQueue::set_operating_mode(RuntimeOrigin::root(), BasicOperatingMode::Halted) + .unwrap(); + + // submit a high priority message from bridge_hub should success + let message = mock_governance_message::(); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_ok!(OutboundQueue::deliver(ticket)); + + // submit a low priority message from asset_hub will fail as pallet is halted + let message = mock_message(1000); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_noop!(OutboundQueue::deliver(ticket), SendError::Halted); + }); +} + +#[test] +fn governance_message_does_not_get_the_chance_to_processed_in_same_block_when_congest_of_low_priority_sibling_messages( +) { + use AggregateMessageOrigin::*; + + let sibling_id: u32 = 1000; + + new_tester().execute_with(|| { + // submit a lot of low priority messages from asset_hub which will need multiple blocks to + // execute(20 messages for each block so 40 required at least 2 blocks) + let max_messages = 40; + for _ in 0..max_messages { + // submit low priority message + let message = mock_message(sibling_id); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + OutboundQueue::deliver(ticket).unwrap(); + } + + let footprint = + MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64))); + assert_eq!(footprint.storage.count, (max_messages) as u64); + + let message = mock_governance_message::(); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + OutboundQueue::deliver(ticket).unwrap(); + + // move to next block + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + // first process 20 messages from sibling channel + let footprint = + MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64))); + assert_eq!(footprint.storage.count, 40 - 20); + + // and governance message does not have the chance to execute in same block + let footprint = MessageQueue::footprint(SnowbridgeV2(primary_governance_origin())); + assert_eq!(footprint.storage.count, 1); + + // move to next block + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + // now governance message get executed in this block + let footprint = MessageQueue::footprint(SnowbridgeV2(primary_governance_origin())); + assert_eq!(footprint.storage.count, 0); + + // and this time process 19 messages from sibling channel so we have 1 message left + let footprint = + MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64))); + assert_eq!(footprint.storage.count, 1); + + // move to the next block, the last 1 message from sibling channel get executed + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + let footprint = + MessageQueue::footprint(SnowbridgeV2(H256::from_low_u64_be(sibling_id as u64))); + assert_eq!(footprint.storage.count, 0); + }); +} + +#[test] +fn encode_digest_item_with_correct_index() { + new_tester().execute_with(|| { + let digest_item: DigestItem = CustomDigestItem::Snowbridge(H256::default()).into(); + let enum_prefix = match digest_item { + DigestItem::Other(data) => data[0], + _ => u8::MAX, + }; + assert_eq!(enum_prefix, 0); + }); +} + +#[test] +fn encode_digest_item() { + new_tester().execute_with(|| { + let digest_item: DigestItem = CustomDigestItem::Snowbridge([5u8; 32].into()).into(); + let digest_item_raw = digest_item.encode(); + assert_eq!(digest_item_raw[0], 0); // DigestItem::Other + assert_eq!(digest_item_raw[2], 0); // CustomDigestItem::Snowbridge + assert_eq!( + digest_item_raw, + [ + 0, 132, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5 + ] + ); + }); +} + +#[test] +fn encode_mock_message() { + let message: Message = mock_message(1000); + let commands: Vec = message + .commands + .into_iter() + .map(|command| CommandWrapper { + kind: command.index(), + gas: ::GasMeter::maximum_dispatch_gas_used_at_most(&command), + payload: command.abi_encode(), + }) + .collect(); + + // print the abi-encoded message and decode with solidity test + let committed_message = InboundMessageWrapper { + origin: FixedBytes::from(message.origin.as_fixed_bytes()), + nonce: 1, + commands, + }; + let message_abi_encoded = committed_message.abi_encode(); + println!("{}", HexDisplay::from(&message_abi_encoded)); +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs new file mode 100644 index 000000000000..db1f567e42fc --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::Pallet; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::ProcessMessage; +use scale_info::TypeInfo; +pub use snowbridge_merkle_tree::MerkleProof; +use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; + +pub type ProcessMessageOriginOf = as ProcessMessage>::Origin; + +/// Pending order +#[derive(Encode, Decode, TypeInfo, Clone, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)] +pub struct PendingOrder { + /// The nonce used to identify the message + pub nonce: u64, + /// The block number in which the message was committed + pub block_number: BlockNumber, + /// The fee + #[codec(compact)] + pub fee: u128, +} diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/weights.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/weights.rs new file mode 100644 index 000000000000..196cc49a4c4d --- /dev/null +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/weights.rs @@ -0,0 +1,89 @@ + +//! Autogenerated weights for `snowbridge-pallet-outbound-queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-19, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `192.168.1.7`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge-pallet-outbound-queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template +// ../parachain/templates/module-weight-template.hbs +// --output +// ../parachain/pallets/outbound-queue/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `snowbridge-pallet-outbound-queue`. +pub trait WeightInfo { + fn do_process_message() -> Weight; + fn commit() -> Weight; + fn commit_single() -> Weight; + fn submit_delivery_proof() -> Weight; +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1) + /// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Nonce (r:1 w:1) + /// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Messages (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured) + fn do_process_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3485` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(39_000_000, 3485) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + fn commit() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(28_000_000, 2579) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn commit_single() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 1586) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn submit_delivery_proof() -> Weight { + Weight::from_parts(70_000_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index 78546e258daa..5aa10e69a01e 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -31,7 +31,7 @@ sp-arithmetic = { workspace = true } bridge-hub-common = { workspace = true } snowbridge-core = { features = ["serde"], workspace = true } -snowbridge-outbound-queue-merkle-tree = { workspace = true } +snowbridge-merkle-tree = { workspace = true } ethabi = { workspace = true } [dev-dependencies] @@ -51,7 +51,7 @@ std = [ "scale-info/std", "serde/std", "snowbridge-core/std", - "snowbridge-outbound-queue-merkle-tree/std", + "snowbridge-merkle-tree/std", "sp-arithmetic/std", "sp-core/std", "sp-io/std", diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md deleted file mode 100644 index a3afef1d6713..000000000000 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Snowbridge Outbound Queue Merkle Tree - -This crate implements a simple binary Merkle Tree utilities required for inter-op with Ethereum -bridge & Solidity contract. diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index d35bdde5a81e..f050db9378a9 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -19,7 +19,7 @@ codec = { features = ["derive"], workspace = true } sp-std = { workspace = true } sp-api = { workspace = true } frame-support = { workspace = true } -snowbridge-outbound-queue-merkle-tree = { workspace = true } +snowbridge-merkle-tree = { workspace = true } snowbridge-core = { workspace = true } [features] @@ -28,7 +28,7 @@ std = [ "codec/std", "frame-support/std", "snowbridge-core/std", - "snowbridge-outbound-queue-merkle-tree/std", + "snowbridge-merkle-tree/std", "sp-api/std", "sp-std/std", ] diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs index e6ddaa439352..ecd2de682268 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs @@ -4,10 +4,10 @@ use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_core::{ - outbound::{Command, Fee}, + outbound::v1::{Command, Fee}, PricingParameters, }; -use snowbridge_outbound_queue_merkle_tree::MerkleProof; +use snowbridge_merkle_tree::MerkleProof; sp_api::decl_runtime_apis! { pub trait OutboundQueueApi where Balance: BalanceT diff --git a/bridges/snowbridge/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs index b904819b1b18..08f4f1561968 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/api.rs @@ -5,10 +5,10 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_core::{ - outbound::{Command, Fee, GasMeter}, + outbound::v1::{Command, Fee, GasMeter}, PricingParameters, }; -use snowbridge_outbound_queue_merkle_tree::{merkle_proof, MerkleProof}; +use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; use sp_core::Get; pub fn prove_message(leaf_index: u64) -> Option diff --git a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs index ee5754e86962..0eff490b1ae4 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs @@ -6,7 +6,7 @@ use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; use frame_benchmarking::v2::*; use snowbridge_core::{ - outbound::{Command, Initializer}, + outbound::v1::{Command, Initializer}, ChannelId, }; use sp_core::{H160, H256}; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs index 9b9dbe854a5e..0d43519167af 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -111,11 +111,10 @@ use frame_support::{ weights::{Weight, WeightToFee}, }; use snowbridge_core::{ - outbound::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, + outbound::v1::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, BasicOperatingMode, ChannelId, }; -use snowbridge_outbound_queue_merkle_tree::merkle_root; -pub use snowbridge_outbound_queue_merkle_tree::MerkleProof; +use snowbridge_merkle_tree::merkle_root; use sp_core::{H256, U256}; use sp_runtime::{ traits::{CheckedDiv, Hash}, diff --git a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs index 0b34893333e4..d7bc4a8bcb5d 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs @@ -10,7 +10,7 @@ use frame_support::{ use snowbridge_core::{ gwei, meth, - outbound::*, + outbound::v1::*, pricing::{PricingParameters, Rewards}, ParaId, PRIMARY_GOVERNANCE_CHANNEL, }; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs index 03be61819973..39b41b1c792a 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs @@ -12,8 +12,8 @@ use frame_support::{ use frame_system::unique; use snowbridge_core::{ outbound::{ - Fee, Message, QueuedMessage, SendError, SendMessage, SendMessageFeeProvider, - VersionedQueuedMessage, + v1::{Fee, Message, QueuedMessage, SendMessage, VersionedQueuedMessage}, + SendError, SendMessageFeeProvider, }, ChannelId, PRIMARY_GOVERNANCE_CHANNEL, }; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs index 4e9ea36e24bc..36227817f368 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/test.rs @@ -10,7 +10,10 @@ use frame_support::{ use codec::Encode; use snowbridge_core::{ - outbound::{Command, SendError, SendMessage}, + outbound::{ + v1::{Command, SendMessage}, + SendError, + }, ParaId, PricingParameters, Rewards, }; use sp_arithmetic::FixedU128; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/types.rs b/bridges/snowbridge/pallets/outbound-queue/src/types.rs index f65a15d3d2f9..76d32fab462a 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/types.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/types.rs @@ -4,15 +4,13 @@ use codec::{Decode, Encode}; use ethabi::Token; use frame_support::traits::ProcessMessage; use scale_info::TypeInfo; +use snowbridge_core::ChannelId; use sp_core::H256; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; use super::Pallet; -use snowbridge_core::ChannelId; -pub use snowbridge_outbound_queue_merkle_tree::MerkleProof; - pub type ProcessMessageOriginOf = as ProcessMessage>::Origin; pub const LOG_TARGET: &str = "snowbridge-outbound-queue"; diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index eb3da095fe85..52cc28b7de75 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -68,7 +68,11 @@ use frame_support::{ use frame_system::pallet_prelude::*; use snowbridge_core::{ meth, - outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage}, + outbound::{ + v1::{Command, Initializer, Message, SendMessage}, + v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, + OperatingMode, SendError, + }, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, @@ -137,7 +141,7 @@ where #[frame_support::pallet] pub mod pallet { use frame_support::dispatch::PostDispatchInfo; - use snowbridge_core::StaticLookup; + use snowbridge_core::{outbound::v2::second_governance_origin, StaticLookup}; use sp_core::U256; use super::*; @@ -152,6 +156,8 @@ pub mod pallet { /// Send messages to Ethereum type OutboundQueue: SendMessage>; + type OutboundQueueV2: SendMessageV2>; + /// Origin check for XCM locations that can create agents type SiblingOrigin: EnsureOrigin; @@ -635,6 +641,34 @@ pub mod pallet { pays_fee: Pays::No, }) } + + /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `location`: Location of the asset (relative to this chain) + /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::register_token())] + pub fn register_token_v2( + origin: OriginFor, + location: Box, + metadata: AssetMetadata, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let location: Location = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + Self::do_register_token_v2(&location, metadata, PaysFee::::No)?; + + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::register_token()), + pays_fee: Pays::No, + }) + } } impl Pallet { @@ -760,6 +794,72 @@ pub mod pallet { Ok(()) } + + pub(crate) fn do_register_token_v2( + location: &Location, + metadata: AssetMetadata, + pays_fee: PaysFee, + ) -> Result<(), DispatchError> { + let ethereum_location = T::EthereumLocation::get(); + // reanchor to Ethereum context + let location = location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + + if !ForeignToNativeId::::contains_key(token_id) { + NativeToForeignId::::insert(location.clone(), token_id); + ForeignToNativeId::::insert(token_id, location.clone()); + } + + let command = CommandV2::RegisterForeignToken { + token_id, + name: metadata.name.into_inner(), + symbol: metadata.symbol.into_inner(), + decimals: metadata.decimals, + }; + Self::send_v2(second_governance_origin(), command, pays_fee)?; + + Self::deposit_event(Event::::RegisterToken { + location: location.clone().into(), + foreign_token_id: token_id, + }); + + Ok(()) + } + + /// Send `command` to the Gateway on the Channel identified by `channel_id` + fn send_v2(origin: H256, command: CommandV2, pays_fee: PaysFee) -> DispatchResult { + let message = MessageV2 { + origin, + id: Default::default(), + fee: Default::default(), + commands: BoundedVec::try_from(vec![command]).unwrap(), + }; + + let (ticket, fee) = + T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; + + let payment = match pays_fee { + PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee.total())), + PaysFee::No => None, + }; + + if let Some((payer, fee)) = payment { + T::Token::transfer( + &payer, + &T::TreasuryAccount::get(), + fee, + Preservation::Preserve, + )?; + } + + T::OutboundQueueV2::deliver(ticket).map_err(|err| Error::::Send(err))?; + Ok(()) + } } impl StaticLookup for Pallet { diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs index 47b089866a53..f20f8886450f 100644 --- a/bridges/snowbridge/pallets/system/src/mock.rs +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -11,8 +11,9 @@ use sp_core::H256; use xcm_executor::traits::ConvertLocation; use snowbridge_core::{ - gwei, meth, outbound::ConstantGasMeter, sibling_sovereign_account, AgentId, AllowSiblingsOnly, - ParaId, PricingParameters, Rewards, + gwei, meth, + outbound::{v1::ConstantGasMeter, v2::DefaultOutboundQueue}, + sibling_sovereign_account, AgentId, AllowSiblingsOnly, ParaId, PricingParameters, Rewards, }; use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256}, @@ -213,6 +214,7 @@ impl crate::Config for Test { type EthereumLocation = EthereumDestination; #[cfg(feature = "runtime-benchmarks")] type Helper = (); + type OutboundQueueV2 = DefaultOutboundQueue; } // Build genesis storage according to the mock runtime. diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index fa37c795b2d1..0e696f0d2256 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -32,6 +32,8 @@ sp-arithmetic = { workspace = true } snowbridge-beacon-primitives = { workspace = true } ethabi = { workspace = true } +alloy-primitives = { features = ["rlp"], workspace = true } +alloy-sol-types = { workspace = true } [dev-dependencies] hex = { workspace = true, default-features = true } @@ -40,6 +42,8 @@ xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] std = [ + "alloy-primitives/std", + "alloy-sol-types/std", "codec/std", "ethabi/std", "frame-support/std", diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 7ad129a52542..88ac8124a15b 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -13,6 +13,7 @@ pub mod location; pub mod operating_mode; pub mod outbound; pub mod pricing; +pub mod reward; pub mod ringbuffer; pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; @@ -37,6 +38,8 @@ pub use operating_mode::BasicOperatingMode; pub use pricing::{PricingParameters, Rewards}; +pub use reward::RewardLedger; + pub fn sibling_sovereign_account(para_id: ParaId) -> T::AccountId where T: frame_system::Config, diff --git a/bridges/snowbridge/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs deleted file mode 100644 index 77770761822a..000000000000 --- a/bridges/snowbridge/primitives/core/src/outbound.rs +++ /dev/null @@ -1,475 +0,0 @@ -use codec::{Decode, Encode}; -use frame_support::PalletError; -use scale_info::TypeInfo; -use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; -use sp_core::{RuntimeDebug, H256}; -pub use v1::{AgentExecuteCommand, Command, Initializer, Message, OperatingMode, QueuedMessage}; - -/// Enqueued outbound messages need to be versioned to prevent data corruption -/// or loss after forkless runtime upgrades -#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(PartialEq))] -pub enum VersionedQueuedMessage { - V1(QueuedMessage), -} - -impl TryFrom for QueuedMessage { - type Error = (); - fn try_from(x: VersionedQueuedMessage) -> Result { - use VersionedQueuedMessage::*; - match x { - V1(x) => Ok(x), - } - } -} - -impl> From for VersionedQueuedMessage { - fn from(x: T) -> Self { - VersionedQueuedMessage::V1(x.into()) - } -} - -mod v1 { - use crate::{pricing::UD60x18, ChannelId}; - use codec::{Decode, Encode}; - use ethabi::Token; - use scale_info::TypeInfo; - use sp_core::{RuntimeDebug, H160, H256, U256}; - use sp_std::{borrow::ToOwned, vec, vec::Vec}; - - /// A message which can be accepted by implementations of `/[`SendMessage`\]` - #[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] - #[cfg_attr(feature = "std", derive(PartialEq))] - pub struct Message { - /// ID for this message. One will be automatically generated if not provided. - /// - /// When this message is created from an XCM message, the ID should be extracted - /// from the `SetTopic` instruction. - /// - /// The ID plays no role in bridge consensus, and is purely meant for message tracing. - pub id: Option, - /// The message channel ID - pub channel_id: ChannelId, - /// The stable ID for a receiving gateway contract - pub command: Command, - } - - /// The operating mode of Channels and Gateway contract on Ethereum. - #[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] - pub enum OperatingMode { - /// Normal operations. Allow sending and receiving messages. - Normal, - /// Reject outbound messages. This allows receiving governance messages but does now allow - /// enqueuing of new messages from the Ethereum side. This can be used to close off an - /// deprecated channel or pause the bridge for upgrade operations. - RejectingOutboundMessages, - } - - /// A command which is executable by the Gateway contract on Ethereum - #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] - #[cfg_attr(feature = "std", derive(PartialEq))] - pub enum Command { - /// Execute a sub-command within an agent for a consensus system in Polkadot - AgentExecute { - /// The ID of the agent - agent_id: H256, - /// The sub-command to be executed - command: AgentExecuteCommand, - }, - /// Upgrade the Gateway contract - Upgrade { - /// Address of the new implementation contract - impl_address: H160, - /// Codehash of the implementation contract - impl_code_hash: H256, - /// Optionally invoke an initializer in the implementation contract - initializer: Option, - }, - /// Create an agent representing a consensus system on Polkadot - CreateAgent { - /// The ID of the agent, derived from the `MultiLocation` of the consensus system on - /// Polkadot - agent_id: H256, - }, - /// Create bidirectional messaging channel to a parachain - CreateChannel { - /// The ID of the channel - channel_id: ChannelId, - /// The agent ID of the parachain - agent_id: H256, - /// Initial operating mode - mode: OperatingMode, - }, - /// Update the configuration of a channel - UpdateChannel { - /// The ID of the channel - channel_id: ChannelId, - /// The new operating mode - mode: OperatingMode, - }, - /// Set the global operating mode of the Gateway contract - SetOperatingMode { - /// The new operating mode - mode: OperatingMode, - }, - /// Transfer ether from an agent contract to a recipient account - TransferNativeFromAgent { - /// The agent ID - agent_id: H256, - /// The recipient of the ether - recipient: H160, - /// The amount to transfer - amount: u128, - }, - /// Set token fees of the Gateway contract - SetTokenTransferFees { - /// The fee(DOT) for the cost of creating asset on AssetHub - create_asset_xcm: u128, - /// The fee(DOT) for the cost of sending asset on AssetHub - transfer_asset_xcm: u128, - /// The fee(Ether) for register token to discourage spamming - register_token: U256, - }, - /// Set pricing parameters - SetPricingParameters { - // ETH/DOT exchange rate - exchange_rate: UD60x18, - // Cost of delivering a message from Ethereum to BridgeHub, in ROC/KSM/DOT - delivery_cost: u128, - // Fee multiplier - multiplier: UD60x18, - }, - /// Transfer ERC20 tokens - TransferNativeToken { - /// ID of the agent - agent_id: H256, - /// Address of the ERC20 token - token: H160, - /// The recipient of the tokens - recipient: H160, - /// The amount of tokens to transfer - amount: u128, - }, - /// Register foreign token from Polkadot - RegisterForeignToken { - /// ID for the token - token_id: H256, - /// Name of the token - name: Vec, - /// Short symbol for the token - symbol: Vec, - /// Number of decimal places - decimals: u8, - }, - /// Mint foreign token from Polkadot - MintForeignToken { - /// ID for the token - token_id: H256, - /// The recipient of the newly minted tokens - recipient: H160, - /// The amount of tokens to mint - amount: u128, - }, - } - - impl Command { - /// Compute the enum variant index - pub fn index(&self) -> u8 { - match self { - Command::AgentExecute { .. } => 0, - Command::Upgrade { .. } => 1, - Command::CreateAgent { .. } => 2, - Command::CreateChannel { .. } => 3, - Command::UpdateChannel { .. } => 4, - Command::SetOperatingMode { .. } => 5, - Command::TransferNativeFromAgent { .. } => 6, - Command::SetTokenTransferFees { .. } => 7, - Command::SetPricingParameters { .. } => 8, - Command::TransferNativeToken { .. } => 9, - Command::RegisterForeignToken { .. } => 10, - Command::MintForeignToken { .. } => 11, - } - } - - /// ABI-encode the Command. - pub fn abi_encode(&self) -> Vec { - match self { - Command::AgentExecute { agent_id, command } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Bytes(command.abi_encode()), - ])]), - Command::Upgrade { impl_address, impl_code_hash, initializer, .. } => - ethabi::encode(&[Token::Tuple(vec![ - Token::Address(*impl_address), - Token::FixedBytes(impl_code_hash.as_bytes().to_owned()), - initializer - .clone() - .map_or(Token::Bytes(vec![]), |i| Token::Bytes(i.params)), - ])]), - Command::CreateAgent { agent_id } => - ethabi::encode(&[Token::Tuple(vec![Token::FixedBytes( - agent_id.as_bytes().to_owned(), - )])]), - Command::CreateChannel { channel_id, agent_id, mode } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(channel_id.as_ref().to_owned()), - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Uint(U256::from((*mode) as u64)), - ])]), - Command::UpdateChannel { channel_id, mode } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(channel_id.as_ref().to_owned()), - Token::Uint(U256::from((*mode) as u64)), - ])]), - Command::SetOperatingMode { mode } => - ethabi::encode(&[Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]), - Command::TransferNativeFromAgent { agent_id, recipient, amount } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])]), - Command::SetTokenTransferFees { - create_asset_xcm, - transfer_asset_xcm, - register_token, - } => ethabi::encode(&[Token::Tuple(vec![ - Token::Uint(U256::from(*create_asset_xcm)), - Token::Uint(U256::from(*transfer_asset_xcm)), - Token::Uint(*register_token), - ])]), - Command::SetPricingParameters { exchange_rate, delivery_cost, multiplier } => - ethabi::encode(&[Token::Tuple(vec![ - Token::Uint(exchange_rate.clone().into_inner()), - Token::Uint(U256::from(*delivery_cost)), - Token::Uint(multiplier.clone().into_inner()), - ])]), - Command::TransferNativeToken { agent_id, token, recipient, amount } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(agent_id.as_bytes().to_owned()), - Token::Address(*token), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])]), - Command::RegisterForeignToken { token_id, name, symbol, decimals } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(token_id.as_bytes().to_owned()), - Token::String(name.to_owned()), - Token::String(symbol.to_owned()), - Token::Uint(U256::from(*decimals)), - ])]), - Command::MintForeignToken { token_id, recipient, amount } => - ethabi::encode(&[Token::Tuple(vec![ - Token::FixedBytes(token_id.as_bytes().to_owned()), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])]), - } - } - } - - /// Representation of a call to the initializer of an implementation contract. - /// The initializer has the following ABI signature: `initialize(bytes)`. - #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] - pub struct Initializer { - /// ABI-encoded params of type `bytes` to pass to the initializer - pub params: Vec, - /// The initializer is allowed to consume this much gas at most. - pub maximum_required_gas: u64, - } - - /// A Sub-command executable within an agent - #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] - #[cfg_attr(feature = "std", derive(PartialEq))] - pub enum AgentExecuteCommand { - /// Transfer ERC20 tokens - TransferToken { - /// Address of the ERC20 token - token: H160, - /// The recipient of the tokens - recipient: H160, - /// The amount of tokens to transfer - amount: u128, - }, - } - - impl AgentExecuteCommand { - fn index(&self) -> u8 { - match self { - AgentExecuteCommand::TransferToken { .. } => 0, - } - } - - /// ABI-encode the sub-command - pub fn abi_encode(&self) -> Vec { - match self { - AgentExecuteCommand::TransferToken { token, recipient, amount } => - ethabi::encode(&[ - Token::Uint(self.index().into()), - Token::Bytes(ethabi::encode(&[ - Token::Address(*token), - Token::Address(*recipient), - Token::Uint(U256::from(*amount)), - ])), - ]), - } - } - } - - /// Message which is awaiting processing in the MessageQueue pallet - #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] - #[cfg_attr(feature = "std", derive(PartialEq))] - pub struct QueuedMessage { - /// Message ID - pub id: H256, - /// Channel ID - pub channel_id: ChannelId, - /// Command to execute in the Gateway contract - pub command: Command, - } -} - -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(PartialEq))] -/// Fee for delivering message -pub struct Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - /// Fee to cover cost of processing the message locally - pub local: Balance, - /// Fee to cover cost processing the message remotely - pub remote: Balance, -} - -impl Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - pub fn total(&self) -> Balance { - self.local.saturating_add(self.remote) - } -} - -impl From<(Balance, Balance)> for Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - fn from((local, remote): (Balance, Balance)) -> Self { - Self { local, remote } - } -} - -/// A trait for sending messages to Ethereum -pub trait SendMessage: SendMessageFeeProvider { - type Ticket: Clone + Encode + Decode; - - /// Validate an outbound message and return a tuple: - /// 1. Ticket for submitting the message - /// 2. Delivery fee - fn validate( - message: &Message, - ) -> Result<(Self::Ticket, Fee<::Balance>), SendError>; - - /// Submit the message ticket for eventual delivery to Ethereum - fn deliver(ticket: Self::Ticket) -> Result; -} - -pub trait Ticket: Encode + Decode + Clone { - fn message_id(&self) -> H256; -} - -/// A trait for getting the local costs associated with sending a message. -pub trait SendMessageFeeProvider { - type Balance: BaseArithmetic + Unsigned + Copy; - - /// The local component of the message processing fees in native currency - fn local_fee() -> Self::Balance; -} - -/// Reasons why sending to Ethereum could not be initiated -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, PalletError, TypeInfo)] -pub enum SendError { - /// Message is too large to be safely executed on Ethereum - MessageTooLarge, - /// The bridge has been halted for maintenance - Halted, - /// Invalid Channel - InvalidChannel, -} - -pub trait GasMeter { - /// All the gas used for submitting a message to Ethereum, minus the cost of dispatching - /// the command within the message - const MAXIMUM_BASE_GAS: u64; - - /// Total gas consumed at most, including verification & dispatch - fn maximum_gas_used_at_most(command: &Command) -> u64 { - Self::MAXIMUM_BASE_GAS + Self::maximum_dispatch_gas_used_at_most(command) - } - - /// Measures the maximum amount of gas a command payload will require to *dispatch*, NOT - /// including validation & verification. - fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64; -} - -/// A meter that assigns a constant amount of gas for the execution of a command -/// -/// The gas figures are extracted from this report: -/// > forge test --match-path test/Gateway.t.sol --gas-report -/// -/// A healthy buffer is added on top of these figures to account for: -/// * The EIP-150 63/64 rule -/// * Future EVM upgrades that may increase gas cost -pub struct ConstantGasMeter; - -impl GasMeter for ConstantGasMeter { - // The base transaction cost, which includes: - // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000 - // for message verification - const MAXIMUM_BASE_GAS: u64 = 185_000; - - fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 { - match command { - Command::CreateAgent { .. } => 275_000, - Command::CreateChannel { .. } => 100_000, - Command::UpdateChannel { .. } => 50_000, - Command::TransferNativeFromAgent { .. } => 60_000, - Command::SetOperatingMode { .. } => 40_000, - Command::AgentExecute { command, .. } => match command { - // Execute IERC20.transferFrom - // - // Worst-case assumptions are important: - // * No gas refund for clearing storage slot of source account in ERC20 contract - // * Assume dest account in ERC20 contract does not yet have a storage slot - // * ERC20.transferFrom possibly does other business logic besides updating balances - AgentExecuteCommand::TransferToken { .. } => 100_000, - }, - Command::Upgrade { initializer, .. } => { - let initializer_max_gas = match *initializer { - Some(Initializer { maximum_required_gas, .. }) => maximum_required_gas, - None => 0, - }; - // total maximum gas must also include the gas used for updating the proxy before - // the the initializer is called. - 50_000 + initializer_max_gas - }, - Command::SetTokenTransferFees { .. } => 60_000, - Command::SetPricingParameters { .. } => 60_000, - Command::TransferNativeToken { .. } => 100_000, - Command::RegisterForeignToken { .. } => 1_200_000, - Command::MintForeignToken { .. } => 100_000, - } - } -} - -impl GasMeter for () { - const MAXIMUM_BASE_GAS: u64 = 1; - - fn maximum_dispatch_gas_used_at_most(_: &Command) -> u64 { - 1 - } -} - -pub const ETHER_DECIMALS: u8 = 18; diff --git a/bridges/snowbridge/primitives/core/src/outbound/mod.rs b/bridges/snowbridge/primitives/core/src/outbound/mod.rs new file mode 100644 index 000000000000..0aa60f479195 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/mod.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound +//! +//! Common traits and types +use codec::{Decode, Encode}; +use frame_support::PalletError; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_core::RuntimeDebug; + +pub mod v1; +pub mod v2; + +/// The operating mode of Channels and Gateway contract on Ethereum. +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum OperatingMode { + /// Normal operations. Allow sending and receiving messages. + Normal, + /// Reject outbound messages. This allows receiving governance messages but does now allow + /// enqueuing of new messages from the Ethereum side. This can be used to close off an + /// deprecated channel or pause the bridge for upgrade operations. + RejectingOutboundMessages, +} + +/// A trait for getting the local costs associated with sending a message. +pub trait SendMessageFeeProvider { + type Balance: BaseArithmetic + Unsigned + Copy; + + /// The local component of the message processing fees in native currency + fn local_fee() -> Self::Balance; +} + +/// Reasons why sending to Ethereum could not be initiated +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, PalletError, TypeInfo)] +pub enum SendError { + /// Message is too large to be safely executed on Ethereum + MessageTooLarge, + /// The bridge has been halted for maintenance + Halted, + /// Invalid Channel + InvalidChannel, +} + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum DryRunError { + ConvertLocationFailed, + ConvertXcmFailed, +} diff --git a/bridges/snowbridge/primitives/core/src/outbound/v1.rs b/bridges/snowbridge/primitives/core/src/outbound/v1.rs new file mode 100644 index 000000000000..037fc21db017 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/v1.rs @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound V1 primitives + +use crate::{ + outbound::{OperatingMode, SendError, SendMessageFeeProvider}, + pricing::UD60x18, + ChannelId, +}; +use codec::{Decode, Encode}; +use ethabi::Token; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_core::{RuntimeDebug, H160, H256, U256}; +use sp_std::{borrow::ToOwned, vec, vec::Vec}; + +/// Enqueued outbound messages need to be versioned to prevent data corruption +/// or loss after forkless runtime upgrades +#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub enum VersionedQueuedMessage { + V1(QueuedMessage), +} + +impl TryFrom for QueuedMessage { + type Error = (); + fn try_from(x: VersionedQueuedMessage) -> Result { + use VersionedQueuedMessage::*; + match x { + V1(x) => Ok(x), + } + } +} + +impl> From for VersionedQueuedMessage { + fn from(x: T) -> Self { + VersionedQueuedMessage::V1(x.into()) + } +} + +/// A message which can be accepted by implementations of `/[`SendMessage`\]` +#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub struct Message { + /// ID for this message. One will be automatically generated if not provided. + /// + /// When this message is created from an XCM message, the ID should be extracted + /// from the `SetTopic` instruction. + /// + /// The ID plays no role in bridge consensus, and is purely meant for message tracing. + pub id: Option, + /// The message channel ID + pub channel_id: ChannelId, + /// The stable ID for a receiving gateway contract + pub command: Command, +} + +/// A command which is executable by the Gateway contract on Ethereum +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub enum Command { + /// Execute a sub-command within an agent for a consensus system in Polkadot + AgentExecute { + /// The ID of the agent + agent_id: H256, + /// The sub-command to be executed + command: AgentExecuteCommand, + }, + /// Upgrade the Gateway contract + Upgrade { + /// Address of the new implementation contract + impl_address: H160, + /// Codehash of the implementation contract + impl_code_hash: H256, + /// Optionally invoke an initializer in the implementation contract + initializer: Option, + }, + /// Create an agent representing a consensus system on Polkadot + CreateAgent { + /// The ID of the agent, derived from the `MultiLocation` of the consensus system on + /// Polkadot + agent_id: H256, + }, + /// Create bidirectional messaging channel to a parachain + CreateChannel { + /// The ID of the channel + channel_id: ChannelId, + /// The agent ID of the parachain + agent_id: H256, + /// Initial operating mode + mode: OperatingMode, + }, + /// Update the configuration of a channel + UpdateChannel { + /// The ID of the channel + channel_id: ChannelId, + /// The new operating mode + mode: OperatingMode, + }, + /// Set the global operating mode of the Gateway contract + SetOperatingMode { + /// The new operating mode + mode: OperatingMode, + }, + /// Transfer ether from an agent contract to a recipient account + TransferNativeFromAgent { + /// The agent ID + agent_id: H256, + /// The recipient of the ether + recipient: H160, + /// The amount to transfer + amount: u128, + }, + /// Set token fees of the Gateway contract + SetTokenTransferFees { + /// The fee(DOT) for the cost of creating asset on AssetHub + create_asset_xcm: u128, + /// The fee(DOT) for the cost of sending asset on AssetHub + transfer_asset_xcm: u128, + /// The fee(Ether) for register token to discourage spamming + register_token: U256, + }, + /// Set pricing parameters + SetPricingParameters { + // ETH/DOT exchange rate + exchange_rate: UD60x18, + // Cost of delivering a message from Ethereum to BridgeHub, in ROC/KSM/DOT + delivery_cost: u128, + // Fee multiplier + multiplier: UD60x18, + }, + /// Transfer ERC20 tokens + TransferNativeToken { + /// ID of the agent + agent_id: H256, + /// Address of the ERC20 token + token: H160, + /// The recipient of the tokens + recipient: H160, + /// The amount of tokens to transfer + amount: u128, + }, + /// Register foreign token from Polkadot + RegisterForeignToken { + /// ID for the token + token_id: H256, + /// Name of the token + name: Vec, + /// Short symbol for the token + symbol: Vec, + /// Number of decimal places + decimals: u8, + }, + /// Mint foreign token from Polkadot + MintForeignToken { + /// ID for the token + token_id: H256, + /// The recipient of the newly minted tokens + recipient: H160, + /// The amount of tokens to mint + amount: u128, + }, +} + +impl Command { + /// Compute the enum variant index + pub fn index(&self) -> u8 { + match self { + Command::AgentExecute { .. } => 0, + Command::Upgrade { .. } => 1, + Command::CreateAgent { .. } => 2, + Command::CreateChannel { .. } => 3, + Command::UpdateChannel { .. } => 4, + Command::SetOperatingMode { .. } => 5, + Command::TransferNativeFromAgent { .. } => 6, + Command::SetTokenTransferFees { .. } => 7, + Command::SetPricingParameters { .. } => 8, + Command::TransferNativeToken { .. } => 9, + Command::RegisterForeignToken { .. } => 10, + Command::MintForeignToken { .. } => 11, + } + } + + /// ABI-encode the Command. + pub fn abi_encode(&self) -> Vec { + match self { + Command::AgentExecute { agent_id, command } => ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Bytes(command.abi_encode()), + ])]), + Command::Upgrade { impl_address, impl_code_hash, initializer, .. } => + ethabi::encode(&[Token::Tuple(vec![ + Token::Address(*impl_address), + Token::FixedBytes(impl_code_hash.as_bytes().to_owned()), + initializer.clone().map_or(Token::Bytes(vec![]), |i| Token::Bytes(i.params)), + ])]), + Command::CreateAgent { agent_id } => + ethabi::encode(&[Token::Tuple(vec![Token::FixedBytes( + agent_id.as_bytes().to_owned(), + )])]), + Command::CreateChannel { channel_id, agent_id, mode } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(channel_id.as_ref().to_owned()), + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Uint(U256::from((*mode) as u64)), + ])]), + Command::UpdateChannel { channel_id, mode } => ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(channel_id.as_ref().to_owned()), + Token::Uint(U256::from((*mode) as u64)), + ])]), + Command::SetOperatingMode { mode } => + ethabi::encode(&[Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]), + Command::TransferNativeFromAgent { agent_id, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + Command::SetTokenTransferFees { + create_asset_xcm, + transfer_asset_xcm, + register_token, + } => ethabi::encode(&[Token::Tuple(vec![ + Token::Uint(U256::from(*create_asset_xcm)), + Token::Uint(U256::from(*transfer_asset_xcm)), + Token::Uint(*register_token), + ])]), + Command::SetPricingParameters { exchange_rate, delivery_cost, multiplier } => + ethabi::encode(&[Token::Tuple(vec![ + Token::Uint(exchange_rate.clone().into_inner()), + Token::Uint(U256::from(*delivery_cost)), + Token::Uint(multiplier.clone().into_inner()), + ])]), + Command::TransferNativeToken { agent_id, token, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Address(*token), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + Command::RegisterForeignToken { token_id, name, symbol, decimals } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(token_id.as_bytes().to_owned()), + Token::String(name.to_owned()), + Token::String(symbol.to_owned()), + Token::Uint(U256::from(*decimals)), + ])]), + Command::MintForeignToken { token_id, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(token_id.as_bytes().to_owned()), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + } + } +} + +/// Representation of a call to the initializer of an implementation contract. +/// The initializer has the following ABI signature: `initialize(bytes)`. +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Initializer { + /// ABI-encoded params of type `bytes` to pass to the initializer + pub params: Vec, + /// The initializer is allowed to consume this much gas at most. + pub maximum_required_gas: u64, +} + +/// A Sub-command executable within an agent +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub enum AgentExecuteCommand { + /// Transfer ERC20 tokens + TransferToken { + /// Address of the ERC20 token + token: H160, + /// The recipient of the tokens + recipient: H160, + /// The amount of tokens to transfer + amount: u128, + }, +} + +impl AgentExecuteCommand { + fn index(&self) -> u8 { + match self { + AgentExecuteCommand::TransferToken { .. } => 0, + } + } + + /// ABI-encode the sub-command + pub fn abi_encode(&self) -> Vec { + match self { + AgentExecuteCommand::TransferToken { token, recipient, amount } => ethabi::encode(&[ + Token::Uint(self.index().into()), + Token::Bytes(ethabi::encode(&[ + Token::Address(*token), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])), + ]), + } + } +} + +/// Message which is awaiting processing in the MessageQueue pallet +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub struct QueuedMessage { + /// Message ID + pub id: H256, + /// Channel ID + pub channel_id: ChannelId, + /// Command to execute in the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +/// Fee for delivering message +pub struct Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + /// Fee to cover cost of processing the message locally + pub local: Balance, + /// Fee to cover cost processing the message remotely + pub remote: Balance, +} + +impl Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn total(&self) -> Balance { + self.local.saturating_add(self.remote) + } +} + +impl From<(Balance, Balance)> for Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + fn from((local, remote): (Balance, Balance)) -> Self { + Self { local, remote } + } +} + +/// A trait for sending messages to Ethereum +pub trait SendMessage: SendMessageFeeProvider { + type Ticket: Clone + Encode + Decode; + + /// Validate an outbound message and return a tuple: + /// 1. Ticket for submitting the message + /// 2. Delivery fee + fn validate( + message: &Message, + ) -> Result<(Self::Ticket, Fee<::Balance>), SendError>; + + /// Submit the message ticket for eventual delivery to Ethereum + fn deliver(ticket: Self::Ticket) -> Result; +} + +pub trait Ticket: Encode + Decode + Clone { + fn message_id(&self) -> H256; +} + +pub trait GasMeter { + /// All the gas used for submitting a message to Ethereum, minus the cost of dispatching + /// the command within the message + const MAXIMUM_BASE_GAS: u64; + + /// Total gas consumed at most, including verification & dispatch + fn maximum_gas_used_at_most(command: &Command) -> u64 { + Self::MAXIMUM_BASE_GAS + Self::maximum_dispatch_gas_used_at_most(command) + } + + /// Measures the maximum amount of gas a command payload will require to *dispatch*, NOT + /// including validation & verification. + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64; +} + +/// A meter that assigns a constant amount of gas for the execution of a command +/// +/// The gas figures are extracted from this report: +/// > forge test --match-path test/Gateway.t.sol --gas-report +/// +/// A healthy buffer is added on top of these figures to account for: +/// * The EIP-150 63/64 rule +/// * Future EVM upgrades that may increase gas cost +pub struct ConstantGasMeter; + +impl GasMeter for ConstantGasMeter { + // The base transaction cost, which includes: + // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000 + // for message verification + const MAXIMUM_BASE_GAS: u64 = 185_000; + + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 { + match command { + Command::CreateAgent { .. } => 275_000, + Command::CreateChannel { .. } => 100_000, + Command::UpdateChannel { .. } => 50_000, + Command::TransferNativeFromAgent { .. } => 60_000, + Command::SetOperatingMode { .. } => 40_000, + Command::AgentExecute { command, .. } => match command { + // Execute IERC20.transferFrom + // + // Worst-case assumptions are important: + // * No gas refund for clearing storage slot of source account in ERC20 contract + // * Assume dest account in ERC20 contract does not yet have a storage slot + // * ERC20.transferFrom possibly does other business logic besides updating balances + AgentExecuteCommand::TransferToken { .. } => 100_000, + }, + Command::Upgrade { initializer, .. } => { + let initializer_max_gas = match *initializer { + Some(Initializer { maximum_required_gas, .. }) => maximum_required_gas, + None => 0, + }; + // total maximum gas must also include the gas used for updating the proxy before + // the the initializer is called. + 50_000 + initializer_max_gas + }, + Command::SetTokenTransferFees { .. } => 60_000, + Command::SetPricingParameters { .. } => 60_000, + Command::TransferNativeToken { .. } => 100_000, + Command::RegisterForeignToken { .. } => 1_200_000, + Command::MintForeignToken { .. } => 100_000, + } + } +} + +impl GasMeter for () { + const MAXIMUM_BASE_GAS: u64 = 1; + + fn maximum_dispatch_gas_used_at_most(_: &Command) -> u64 { + 1 + } +} + +pub const ETHER_DECIMALS: u8 = 18; diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs new file mode 100644 index 000000000000..4443a6ea5297 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Outbound V2 primitives + +use crate::outbound::{OperatingMode, SendError, SendMessageFeeProvider}; +use alloy_sol_types::sol; +use codec::{Decode, Encode}; +use frame_support::{pallet_prelude::ConstU32, BoundedVec}; +use hex_literal::hex; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_core::{RuntimeDebug, H160, H256}; +use sp_std::{vec, vec::Vec}; + +use alloy_primitives::{Address, FixedBytes}; +use alloy_sol_types::SolValue; + +sol! { + struct InboundMessageWrapper { + // origin + bytes32 origin; + // Message nonce + uint64 nonce; + // Commands + CommandWrapper[] commands; + } + + #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)] + struct CommandWrapper { + uint8 kind; + uint64 gas; + bytes payload; + } + + // Payload for Upgrade + struct UpgradeParams { + // The address of the implementation contract + address implAddress; + // Codehash of the new implementation contract. + bytes32 implCodeHash; + // Parameters used to upgrade storage of the gateway + bytes initParams; + } + + // Payload for CreateAgent + struct CreateAgentParams { + /// @dev The agent ID of the consensus system + bytes32 agentID; + } + + // Payload for SetOperatingMode instruction + struct SetOperatingModeParams { + /// The new operating mode + uint8 mode; + } + + // Payload for NativeTokenUnlock instruction + struct UnlockNativeTokenParams { + // Token address + address token; + // Recipient address + address recipient; + // Amount to unlock + uint128 amount; + } + + // Payload for RegisterForeignToken + struct RegisterForeignTokenParams { + /// @dev The token ID (hash of stable location id of token) + bytes32 foreignTokenID; + /// @dev The name of the token + bytes name; + /// @dev The symbol of the token + bytes symbol; + /// @dev The decimal of the token + uint8 decimals; + } + + // Payload for MintForeignTokenParams instruction + struct MintForeignTokenParams { + // Foreign token ID + bytes32 foreignTokenID; + // Recipient address + address recipient; + // Amount to mint + uint128 amount; + } +} + +#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] +pub struct InboundMessage { + /// Origin + pub origin: H256, + /// Nonce + pub nonce: u64, + /// Commands + pub commands: BoundedVec>, +} + +pub const MAX_COMMANDS: u32 = 8; + +/// A message which can be accepted by implementations of `/[`SendMessage`\]` +#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] +pub struct Message { + /// Origin + pub origin: H256, + /// ID + pub id: H256, + /// Fee + pub fee: u128, + /// Commands + pub commands: BoundedVec>, +} + +/// A command which is executable by the Gateway contract on Ethereum +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub enum Command { + /// Upgrade the Gateway contract + Upgrade { + /// Address of the new implementation contract + impl_address: H160, + /// Codehash of the implementation contract + impl_code_hash: H256, + /// Optionally invoke an initializer in the implementation contract + initializer: Option, + }, + /// Create an agent representing a consensus system on Polkadot + CreateAgent { + /// The ID of the agent, derived from the `MultiLocation` of the consensus system on + /// Polkadot + agent_id: H256, + }, + /// Set the global operating mode of the Gateway contract + SetOperatingMode { + /// The new operating mode + mode: OperatingMode, + }, + /// Unlock ERC20 tokens + UnlockNativeToken { + /// ID of the agent + agent_id: H256, + /// Address of the ERC20 token + token: H160, + /// The recipient of the tokens + recipient: H160, + /// The amount of tokens to transfer + amount: u128, + }, + /// Register foreign token from Polkadot + RegisterForeignToken { + /// ID for the token + token_id: H256, + /// Name of the token + name: Vec, + /// Short symbol for the token + symbol: Vec, + /// Number of decimal places + decimals: u8, + }, + /// Mint foreign token from Polkadot + MintForeignToken { + /// ID for the token + token_id: H256, + /// The recipient of the newly minted tokens + recipient: H160, + /// The amount of tokens to mint + amount: u128, + }, +} + +impl Command { + /// Compute the enum variant index + pub fn index(&self) -> u8 { + match self { + Command::Upgrade { .. } => 0, + Command::SetOperatingMode { .. } => 1, + Command::UnlockNativeToken { .. } => 2, + Command::RegisterForeignToken { .. } => 3, + Command::MintForeignToken { .. } => 4, + Command::CreateAgent { .. } => 5, + } + } + + /// ABI-encode the Command. + pub fn abi_encode(&self) -> Vec { + match self { + Command::Upgrade { impl_address, impl_code_hash, initializer, .. } => UpgradeParams { + implAddress: Address::from(impl_address.as_fixed_bytes()), + implCodeHash: FixedBytes::from(impl_code_hash.as_fixed_bytes()), + initParams: initializer.clone().map_or(vec![], |i| i.params), + } + .abi_encode(), + Command::CreateAgent { agent_id } => + CreateAgentParams { agentID: FixedBytes::from(agent_id.as_fixed_bytes()) } + .abi_encode(), + Command::SetOperatingMode { mode } => + SetOperatingModeParams { mode: (*mode) as u8 }.abi_encode(), + Command::UnlockNativeToken { token, recipient, amount, .. } => + UnlockNativeTokenParams { + token: Address::from(token.as_fixed_bytes()), + recipient: Address::from(recipient.as_fixed_bytes()), + amount: *amount, + } + .abi_encode(), + Command::RegisterForeignToken { token_id, name, symbol, decimals } => + RegisterForeignTokenParams { + foreignTokenID: FixedBytes::from(token_id.as_fixed_bytes()), + name: name.to_vec(), + symbol: symbol.to_vec(), + decimals: *decimals, + } + .abi_encode(), + Command::MintForeignToken { token_id, recipient, amount } => MintForeignTokenParams { + foreignTokenID: FixedBytes::from(token_id.as_fixed_bytes()), + recipient: Address::from(recipient.as_fixed_bytes()), + amount: *amount, + } + .abi_encode(), + } + } +} + +/// Representation of a call to the initializer of an implementation contract. +/// The initializer has the following ABI signature: `initialize(bytes)`. +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Initializer { + /// ABI-encoded params of type `bytes` to pass to the initializer + pub params: Vec, + /// The initializer is allowed to consume this much gas at most. + pub maximum_required_gas: u64, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +/// Fee for delivering message +pub struct Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + /// Fee to cover cost of processing the message locally + pub local: Balance, +} + +impl Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn total(&self) -> Balance { + self.local + } +} + +impl From for Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + fn from(local: Balance) -> Self { + Self { local } + } +} + +pub trait SendMessage: SendMessageFeeProvider { + type Ticket: Clone + Encode + Decode; + + /// Validate an outbound message and return a tuple: + /// 1. Ticket for submitting the message + /// 2. Delivery fee + fn validate( + message: &Message, + ) -> Result<(Self::Ticket, Fee<::Balance>), SendError>; + + /// Submit the message ticket for eventual delivery to Ethereum + fn deliver(ticket: Self::Ticket) -> Result; +} + +pub struct DefaultOutboundQueue; +impl SendMessage for DefaultOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Ok(((), Fee { local: Default::default() })) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for DefaultOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + Default::default() + } +} + +pub trait GasMeter { + /// Measures the maximum amount of gas a command payload will require to *dispatch*, NOT + /// including validation & verification. + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64; +} + +/// A meter that assigns a constant amount of gas for the execution of a command +/// +/// The gas figures are extracted from this report: +/// > forge test --match-path test/Gateway.t.sol --gas-report +/// +/// A healthy buffer is added on top of these figures to account for: +/// * The EIP-150 63/64 rule +/// * Future EVM upgrades that may increase gas cost +pub struct ConstantGasMeter; + +impl GasMeter for ConstantGasMeter { + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 { + match command { + Command::CreateAgent { .. } => 275_000, + Command::SetOperatingMode { .. } => 40_000, + Command::Upgrade { initializer, .. } => { + let initializer_max_gas = match *initializer { + Some(Initializer { maximum_required_gas, .. }) => maximum_required_gas, + None => 0, + }; + // total maximum gas must also include the gas used for updating the proxy before + // the the initializer is called. + 50_000 + initializer_max_gas + }, + Command::UnlockNativeToken { .. } => 100_000, + Command::RegisterForeignToken { .. } => 1_200_000, + Command::MintForeignToken { .. } => 100_000, + } + } +} + +impl GasMeter for () { + fn maximum_dispatch_gas_used_at_most(_: &Command) -> u64 { + 1 + } +} + +// Origin for high-priority governance commands +pub fn primary_governance_origin() -> H256 { + hex!("0000000000000000000000000000000000000000000000000000000000000001").into() +} + +// Origin for lower-priority governance commands +pub fn second_governance_origin() -> H256 { + hex!("0000000000000000000000000000000000000000000000000000000000000002").into() +} diff --git a/bridges/snowbridge/primitives/core/src/reward.rs b/bridges/snowbridge/primitives/core/src/reward.rs new file mode 100644 index 000000000000..80e0d9b492d8 --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/reward.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +use frame_support::pallet_prelude::DispatchResult; + +pub trait RewardLedger { + // Deposit reward which can later be claimed by `account` + fn deposit(account: AccountId, value: Balance) -> DispatchResult; +} + +impl RewardLedger for () { + fn deposit(_: AccountId, _: Balance) -> DispatchResult { + Ok(()) + } +} diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/primitives/merkle-tree/Cargo.toml similarity index 76% rename from bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml rename to bridges/snowbridge/primitives/merkle-tree/Cargo.toml index 16241428df80..f1e3efd80a3d 100644 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml +++ b/bridges/snowbridge/primitives/merkle-tree/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "snowbridge-outbound-queue-merkle-tree" -description = "Snowbridge Outbound Queue Merkle Tree" -version = "0.3.0" +name = "snowbridge-merkle-tree" +description = "Snowbridge Merkle Tree" +version = "0.2.0" authors = ["Snowfork "] edition.workspace = true repository.workspace = true @@ -11,15 +11,11 @@ categories = ["cryptography::cryptocurrencies"] [lints] workspace = true -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -codec = { features = ["derive"], workspace = true } +codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } - -sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-core = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/primitives/merkle-tree/README.md b/bridges/snowbridge/primitives/merkle-tree/README.md new file mode 100644 index 000000000000..a9c17ad4f2d1 --- /dev/null +++ b/bridges/snowbridge/primitives/merkle-tree/README.md @@ -0,0 +1,3 @@ +# Merkle-Tree Primitives + +Contains the custom merkle tree implementation optimized for Ethereum. diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/primitives/merkle-tree/src/lib.rs similarity index 100% rename from bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs rename to bridges/snowbridge/primitives/merkle-tree/src/lib.rs diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index ee8d481cec12..664f2dbf7930 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -24,6 +24,7 @@ sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-builder = { workspace = true } snowbridge-core = { workspace = true } @@ -43,6 +44,7 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] @@ -50,5 +52,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index e03560f66e24..abd32aa3897f 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -1,458 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Converts messages from Ethereum to XCM messages +// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. -#[cfg(test)] -mod tests; +pub mod v1; +pub mod v2; -use codec::{Decode, Encode}; -use core::marker::PhantomData; -use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; -use scale_info::TypeInfo; -use snowbridge_core::TokenId; -use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_io::hashing::blake2_256; -use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; -use sp_std::prelude::*; -use xcm::prelude::{Junction::AccountKey20, *}; +use codec::Encode; +use sp_core::blake2_256; +use sp_std::marker::PhantomData; +use xcm::prelude::{AccountKey20, Ethereum, GlobalConsensus, Location}; use xcm_executor::traits::ConvertLocation; -const MINIMUM_DEPOSIT: u128 = 1; - -/// Messages from Ethereum are versioned. This is because in future, -/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. -/// Instead having BridgeHub transcode the messages into XCM. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum VersionedMessage { - V1(MessageV1), -} - -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are -/// self-contained, in that they can be transcoded using only information in the message. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, - /// The command originating from the Gateway contract - pub command: Command, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, -} - -/// Destination for bridged tokens -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, -} - -pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, -} - -/// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] -pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, -} - -/// convert the inbound message to xcm which will be forwarded to the destination chain -pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; -} - -pub type CallIndex = [u8; 2]; - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > ConvertMessage - for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), - } - } -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > - MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - // Forwarding to a destination parachain is not allowed for PNA and is validated on the - // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) - } -} - pub struct EthereumLocationsConverterFor(PhantomData); impl ConvertLocation for EthereumLocationsConverterFor where @@ -477,3 +35,5 @@ impl EthereumLocationsConverterFor { (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) } } + +pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs deleted file mode 100644 index 786aa594f653..000000000000 --- a/bridges/snowbridge/primitives/router/src/inbound/tests.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::EthereumLocationsConverterFor; -use crate::inbound::CallIndex; -use frame_support::{assert_ok, parameter_types}; -use hex_literal::hex; -use xcm::prelude::*; -use xcm_executor::traits::ConvertLocation; - -const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - -parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; -} - -#[test] -fn test_ethereum_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - - let account = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location).unwrap(); - - assert_eq!(account, expected_account); -} - -#[test] -fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("9038d35aba0e78e072d29b2d65be9df5bb4d7d94b4609c9cf98ea8e66e544052"); - let contract_location = Location::new( - 2, - [GlobalConsensus(NETWORK), AccountKey20 { network: None, key: [123u8; 20] }], - ); - - let account = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location).unwrap(); - - assert_eq!(account, expected_account); -} - -#[test] -fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - - assert_eq!( - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), - None, - ); -} - -#[test] -fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!(reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context)); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); - } -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs new file mode 100644 index 000000000000..73e5f5ada939 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; +use sp_std::prelude::*; +use xcm::prelude::{Junction::AccountKey20, *}; + +const MINIMUM_DEPOSIT: u128 = 1; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V1(MessageV1), +} + +/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct MessageV1 { + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Command { + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Ethereum token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, +} + +/// Destination for bridged tokens +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Destination { + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, +} + +pub struct MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + )>, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The message version is not supported for conversion. + UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, +} + +/// convert the inbound message to xcm which will be forwarded to the destination chain +pub trait ConvertMessage { + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > ConvertMessage + for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + type Balance = Balance; + type AccountId = AccountId; + + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), + } + } +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location = Location::new(2, GlobalConsensus(network)); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + // Forward message id to Asset Hub + SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + message_id: H256, + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + (None, Location::new(0, [AccountId32 { network: None, id }]), 0), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountId32 { network: None, id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountKey20 { network: None, key: id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: Asset = (Location::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); + + instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + dest: Location::new(1, [Parachain(dest_para_id)]), + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + }, + } + + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } +} + +#[cfg(test)] +mod tests { + use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; + } + + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( + reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) + ); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs new file mode 100644 index 000000000000..73e5f5ada939 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; +use sp_std::prelude::*; +use xcm::prelude::{Junction::AccountKey20, *}; + +const MINIMUM_DEPOSIT: u128 = 1; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V1(MessageV1), +} + +/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct MessageV1 { + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Command { + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Ethereum token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, +} + +/// Destination for bridged tokens +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Destination { + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, +} + +pub struct MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + )>, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The message version is not supported for conversion. + UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, +} + +/// convert the inbound message to xcm which will be forwarded to the destination chain +pub trait ConvertMessage { + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > ConvertMessage + for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + type Balance = Balance; + type AccountId = AccountId; + + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), + } + } +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location = Location::new(2, GlobalConsensus(network)); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + // Forward message id to Asset Hub + SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + message_id: H256, + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + (None, Location::new(0, [AccountId32 { network: None, id }]), 0), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountId32 { network: None, id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountKey20 { network: None, key: id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: Asset = (Location::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); + + instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + dest: Location::new(1, [Parachain(dest_para_id)]), + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + }, + } + + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } +} + +#[cfg(test)] +mod tests { + use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; + } + + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( + reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) + ); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 3b5dbdb77c89..22756b222812 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -1,423 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Converts XCM messages into simpler commands that can be processed by the Gateway contract - -#[cfg(test)] -mod tests; - -use core::slice::Iter; - -use codec::{Decode, Encode}; - -use frame_support::{ensure, traits::Get}; -use snowbridge_core::{ - outbound::{AgentExecuteCommand, Command, Message, SendMessage}, - AgentId, ChannelId, ParaId, TokenId, TokenIdOf, -}; -use sp_core::{H160, H256}; -use sp_runtime::traits::MaybeEquivalence; -use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; -use xcm::prelude::*; -use xcm_executor::traits::{ConvertLocation, ExportXcm}; - -pub struct EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, ->( - PhantomData<( - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - )>, -); - -impl - ExportXcm - for EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - > -where - UniversalLocation: Get, - EthereumNetwork: Get, - OutboundQueue: SendMessage, - AgentHashedDescription: ConvertLocation, - ConvertAssetId: MaybeEquivalence, -{ - type Ticket = (Vec, XcmHash); - - fn validate( - network: NetworkId, - _channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - let expected_network = EthereumNetwork::get(); - let universal_location = UniversalLocation::get(); - - if network != expected_network { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning destination to avoid modifying the value so subsequent exporters can use it. - let dest = destination.clone().take().ok_or(SendError::MissingArgument)?; - if dest != Here { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, local_sub) = universal_source.clone() - .take() - .ok_or_else(|| { - log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); - SendError::MissingArgument - })? - .split_global() - .map_err(|()| { - log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); - SendError::NotApplicable - })?; - - if Ok(local_net) != universal_location.global_consensus() { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}."); - return Err(SendError::NotApplicable) - } - - let para_id = match local_sub.as_slice() { - [Parachain(para_id)] => *para_id, - _ => { - log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); - return Err(SendError::NotApplicable) - }, - }; - - let source_location = Location::new(1, local_sub.clone()); - - let agent_id = match AgentHashedDescription::convert_location(&source_location) { - Some(id) => id, - None => { - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); - return Err(SendError::NotApplicable) - }, - }; - - let message = message.take().ok_or_else(|| { - log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); - SendError::MissingArgument - })?; - - let mut converter = - XcmConverter::::new(&message, expected_network, agent_id); - let (command, message_id) = converter.convert().map_err(|err|{ - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); - SendError::Unroutable - })?; - - let channel_id: ChannelId = ParaId::from(para_id).into(); - - let outbound_message = Message { id: Some(message_id.into()), channel_id, command }; - - // validate the message - let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { - log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); - SendError::Unroutable - })?; - - // convert fee to Asset - let fee = Asset::from((Location::parent(), fee.total())).into(); - - Ok(((ticket.encode(), message_id), fee)) - } - - fn deliver(blob: (Vec, XcmHash)) -> Result { - let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) - .map_err(|_| { - log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error"); - SendError::NotApplicable - })?; - - let message_id = OutboundQueue::deliver(ticket).map_err(|_| { - log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed"); - SendError::Transport("other transport error") - })?; - - log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}."); - Ok(message_id.into()) - } -} - -/// Errors that can be thrown to the pattern matching step. -#[derive(PartialEq, Debug)] -enum XcmConverterError { - UnexpectedEndOfXcm, - EndOfXcmMessageExpected, - WithdrawAssetExpected, - DepositAssetExpected, - NoReserveAssets, - FilterDoesNotConsumeAllAssets, - TooManyAssets, - ZeroAssetTransfer, - BeneficiaryResolutionFailed, - AssetResolutionFailed, - InvalidFeeAsset, - SetTopicExpected, - ReserveAssetDepositedExpected, - InvalidAsset, - UnexpectedInstruction, -} - -macro_rules! match_expression { - ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { - match $expression { - $( $pattern )|+ $( if $guard )? => Some($value), - _ => None, - } - }; -} - -struct XcmConverter<'a, ConvertAssetId, Call> { - iter: Peekable>>, - ethereum_network: NetworkId, - agent_id: AgentId, - _marker: PhantomData, -} -impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> -where - ConvertAssetId: MaybeEquivalence, -{ - fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { - Self { - iter: message.inner().iter().peekable(), - ethereum_network, - agent_id, - _marker: Default::default(), - } - } - - fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { - let result = match self.peek() { - Ok(ReserveAssetDeposited { .. }) => self.make_mint_foreign_token_command(), - // Get withdraw/deposit and make native tokens create message. - Ok(WithdrawAsset { .. }) => self.make_unlock_native_token_command(), - Err(e) => Err(e), - _ => return Err(XcmConverterError::UnexpectedInstruction), - }?; - - // All xcm instructions must be consumed before exit. - if self.next().is_ok() { - return Err(XcmConverterError::EndOfXcmMessageExpected) - } - - Ok(result) - } - - fn make_unlock_native_token_command( - &mut self, - ) -> Result<(Command, [u8; 32]), XcmConverterError> { - use XcmConverterError::*; - - // Get the reserve assets from WithdrawAsset. - let reserve_assets = - match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) - .ok_or(WithdrawAssetExpected)?; - - // Check if clear origin exists and skip over it. - if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { - let _ = self.next(); - } - - // Get the fee asset item from BuyExecution or continue parsing. - let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); - if fee_asset.is_some() { - let _ = self.next(); - } - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if reserve_assets.len() == 0 { - return Err(NoReserveAssets) - } - - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - - // Fees are collected on AH, up front and directly from the user, to cover the - // complete cost of the transfer. Any additional fees provided in the XCM program are - // refunded to the beneficiary. We only validate the fee here if its provided to make sure - // the XCM program is well formed. Another way to think about this from an XCM perspective - // would be that the user offered to pay X amount in fees, but we charge 0 of that X amount - // (no fee) and refund X to the user. - if let Some(fee_asset) = fee_asset { - // The fee asset must be the same as the reserve asset. - if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { - return Err(InvalidFeeAsset) - } - } - - let (token, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - match inner_location.unpack() { - (0, [AccountKey20 { network, key }]) if self.network_matches(network) => - Some((H160(*key), *amount)), - _ => None, - }, - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - // Check if there is a SetTopic and skip over it if found. - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - Ok(( - Command::AgentExecute { - agent_id: self.agent_id, - command: AgentExecuteCommand::TransferToken { token, recipient, amount }, - }, - *topic_id, - )) - } - - fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { - self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { - self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn network_matches(&self, network: &Option) -> bool { - if let Some(network) = network { - *network == self.ethereum_network - } else { - true - } - } - - /// Convert the xcm for Polkadot-native token from AH into the Command - /// To match transfers of Polkadot-native tokens, we expect an input of the form: - /// # ReserveAssetDeposited - /// # ClearOrigin - /// # BuyExecution - /// # DepositAsset - /// # SetTopic - fn make_mint_foreign_token_command( - &mut self, - ) -> Result<(Command, [u8; 32]), XcmConverterError> { - use XcmConverterError::*; - - // Get the reserve assets. - let reserve_assets = - match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) - .ok_or(ReserveAssetDepositedExpected)?; - - // Check if clear origin exists and skip over it. - if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { - let _ = self.next(); - } - - // Get the fee asset item from BuyExecution or continue parsing. - let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); - if fee_asset.is_some() { - let _ = self.next(); - } - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if reserve_assets.len() == 0 { - return Err(NoReserveAssets) - } - - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - - // Fees are collected on AH, up front and directly from the user, to cover the - // complete cost of the transfer. Any additional fees provided in the XCM program are - // refunded to the beneficiary. We only validate the fee here if its provided to make sure - // the XCM program is well formed. Another way to think about this from an XCM perspective - // would be that the user offered to pay X amount in fees, but we charge 0 of that X amount - // (no fee) and refund X to the user. - if let Some(fee_asset) = fee_asset { - // The fee asset must be the same as the reserve asset. - if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { - return Err(InvalidFeeAsset) - } - } - - let (asset_id, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - Some((inner_location.clone(), *amount)), - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; - - let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; - - ensure!(asset_id == expected_asset_id, InvalidAsset); - - // Check if there is a SetTopic and skip over it if found. - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - Ok((Command::MintForeignToken { token_id, recipient, amount }, *topic_id)) - } -} +// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +pub mod v1; +pub mod v2; diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs deleted file mode 100644 index 44f81ce31b3a..000000000000 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ /dev/null @@ -1,1274 +0,0 @@ -use frame_support::parameter_types; -use hex_literal::hex; -use snowbridge_core::{ - outbound::{Fee, SendError, SendMessageFeeProvider}, - AgentIdOf, -}; -use sp_std::default::Default; -use xcm::{ - latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, - prelude::SendError as XcmSendError, -}; - -use super::*; - -parameter_types! { - const MaxMessageSize: u32 = u32::MAX; - const RelayNetwork: NetworkId = Polkadot; - UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); - const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; - const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; -} - -struct MockOkOutboundQueue; -impl SendMessage for MockOkOutboundQueue { - type Ticket = (); - - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Ok(((), Fee { local: 1, remote: 1 })) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } -} - -impl SendMessageFeeProvider for MockOkOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } -} -struct MockErrOutboundQueue; -impl SendMessage for MockErrOutboundQueue { - type Ticket = (); - - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Err(SendError::MessageTooLarge) - } - - fn deliver(_: Self::Ticket) -> Result { - Err(SendError::MessageTooLarge) - } -} - -impl SendMessageFeeProvider for MockErrOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } -} - -pub struct MockTokenIdConvert; -impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) - } - fn convert_back(_loc: &Location) -> Option { - None - } -} - -#[test] -fn exporter_validate_with_unknown_network_yields_not_applicable() { - let network = Ethereum { chain_id: 1337 }; - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_with_invalid_destination_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); -} - -#[test] -fn exporter_validate_with_x8_destination_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Some( - [OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild] - .into(), - ); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_without_universal_source_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); -} - -#[test] -fn exporter_validate_without_global_universal_location_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_without_global_bridge_location_yields_not_applicable() { - let network = NonBridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_with_remote_universal_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Kusama), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_without_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); -} - -#[test] -fn exporter_validate_without_xcm_message_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); -} - -#[test] -fn exporter_validate_with_max_target_fee_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(fees), - BuyExecution { fees: fee, weight_limit: Unlimited }, - WithdrawAsset(assets), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } - .into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::Unroutable)); -} - -#[test] -fn exporter_validate_with_unparsable_xcm_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - - let mut message: Option> = - Some(vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into()); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::Unroutable)); -} - -#[test] -fn exporter_validate_xcm_success_case_1() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert!(result.is_ok()); -} - -#[test] -fn exporter_deliver_with_submit_failure_yields_unroutable() { - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockErrOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); - assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) -} - -#[test] -fn xcm_converter_convert_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_convert_without_buy_execution_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(All); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - ClearTopic, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); -} - -#[test] -fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); - - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); -} - -#[test] -fn xcm_converter_with_different_fee_asset_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = - Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); -} - -#[test] -fn xcm_converter_with_fees_greater_than_reserve_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); -} - -#[test] -fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { - let network = BridgedNetwork::get(); - - let message: Xcm<()> = vec![].into(); - - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); -} - -#[test] -fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ClearError, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); -} - -#[test] -fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); -} - -#[test] -fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); -} - -#[test] -fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![].into(); - let filter: AssetFilter = assets.clone().into(); - - let fee = Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); -} - -#[test] -fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { - let network = BridgedNetwork::get(); - - let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![ - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), - fun: Fungible(1000), - }, - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), - fun: Fungible(500), - }, - ] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); -} - -#[test] -fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); -} - -#[test] -fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(0), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); -} - -#[test] -fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); -} - -#[test] -fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); -} - -#[test] -fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }].into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); -} - -#[test] -fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_address: [u8; 32] = - hex!("2000000000000000000000000000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: [ - GlobalConsensus(Polkadot), - Parachain(1000), - AccountId32 { network: Some(Polkadot), id: beneficiary_address }, - ] - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); -} - -#[test] -fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed() -{ - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { - network: Some(Ethereum { chain_id: 2 }), - key: beneficiary_address, - } - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); -} - -#[test] -fn test_describe_asset_hub() { - let legacy_location: Location = Location::new(0, [Parachain(1000)]); - let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); - assert_eq!( - legacy_agent_id, - hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() - ); - let location: Location = Location::new(1, [Parachain(1000)]); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() - ) -} - -#[test] -fn test_describe_here() { - let location: Location = Location::new(0, []); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() - ) -} - -#[test] -fn xcm_converter_transfer_native_token_success() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); - let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = - Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); -} - -#[test] -fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - // Invalid asset location from a different consensus - let asset_location = - Location { parents: 2, interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into() }; - - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); -} - -#[test] -fn exporter_validate_with_invalid_dest_does_not_alter_destination() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Parachain(1000).into(); - - let universal_source: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); -} - -#[test] -fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Here.into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); -} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v1.rs b/bridges/snowbridge/primitives/router/src/outbound/v1.rs new file mode 100644 index 000000000000..f952d5c613f9 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/v1.rs @@ -0,0 +1,1703 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +use core::slice::Iter; + +use codec::{Decode, Encode}; + +use frame_support::{ensure, traits::Get}; +use snowbridge_core::{ + outbound::v1::{AgentExecuteCommand, Command, Message, SendMessage}, + AgentId, ChannelId, ParaId, TokenId, TokenIdOf, +}; +use sp_core::{H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + )>, +); + +impl + ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + > +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().take().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, local_sub) = universal_source.clone() + .take() + .ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); + SendError::NotApplicable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let para_id = match local_sub.as_slice() { + [Parachain(para_id)] => *para_id, + _ => { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); + return Err(SendError::NotApplicable) + }, + }; + + let source_location = Location::new(1, local_sub.clone()); + + let agent_id = match AgentHashedDescription::convert_location(&source_location) { + Some(id) => id, + None => { + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); + return Err(SendError::NotApplicable) + }, + }; + + let message = message.clone().ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); + SendError::MissingArgument + })?; + + let mut converter = + XcmConverter::::new(&message, expected_network, agent_id); + let (command, message_id) = converter.convert().map_err(|err|{ + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + let channel_id: ChannelId = ParaId::from(para_id).into(); + + let outbound_message = Message { id: Some(message_id.into()), channel_id, command }; + + // validate the message + let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + // convert fee to Asset + let fee = Asset::from((Location::parent(), fee.total())).into(); + + Ok(((ticket.encode(), message_id), fee)) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// Errors that can be thrown to the pattern matching step. +#[derive(PartialEq, Debug)] +enum XcmConverterError { + UnexpectedEndOfXcm, + EndOfXcmMessageExpected, + WithdrawAssetExpected, + DepositAssetExpected, + NoReserveAssets, + FilterDoesNotConsumeAllAssets, + TooManyAssets, + ZeroAssetTransfer, + BeneficiaryResolutionFailed, + AssetResolutionFailed, + InvalidFeeAsset, + SetTopicExpected, + ReserveAssetDepositedExpected, + InvalidAsset, + UnexpectedInstruction, +} + +macro_rules! match_expression { + ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => Some($value), + _ => None, + } + }; +} + +struct XcmConverter<'a, ConvertAssetId, Call> { + iter: Peekable>>, + ethereum_network: NetworkId, + agent_id: AgentId, + _marker: PhantomData, +} +impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> +where + ConvertAssetId: MaybeEquivalence, +{ + fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { + Self { + iter: message.inner().iter().peekable(), + ethereum_network, + agent_id, + _marker: Default::default(), + } + } + + fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + let result = match self.peek() { + Ok(ReserveAssetDeposited { .. }) => self.send_native_tokens_message(), + // Get withdraw/deposit and make native tokens create message. + Ok(WithdrawAsset { .. }) => self.send_tokens_message(), + Err(e) => Err(e), + _ => return Err(XcmConverterError::UnexpectedInstruction), + }?; + + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(XcmConverterError::EndOfXcmMessageExpected) + } + + Ok(result) + } + + fn send_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets from WithdrawAsset. + let reserve_assets = + match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) + .ok_or(WithdrawAssetExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // If there was a fee specified verify it. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (token, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // Check if there is a SetTopic. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok(( + Command::AgentExecute { + agent_id: self.agent_id, + command: AgentExecuteCommand::TransferToken { token, recipient, amount }, + }, + *topic_id, + )) + } + + fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { + self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { + self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn network_matches(&self, network: &Option) -> bool { + if let Some(network) = network { + *network == self.ethereum_network + } else { + true + } + } + + /// Convert the xcm for Polkadot-native token from AH into the Command + /// To match transfers of Polkadot-native tokens, we expect an input of the form: + /// # ReserveAssetDeposited + /// # ClearOrigin + /// # BuyExecution + /// # DepositAsset + /// # SetTopic + fn send_native_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets. + let reserve_assets = + match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) + .ok_or(ReserveAssetDepositedExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // If there was a fee specified verify it. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (asset_id, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + + ensure!(asset_id == expected_asset_id, InvalidAsset); + + // Check if there is a SetTopic. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok((Command::MintForeignToken { token_id, recipient, amount }, *topic_id)) + } +} + +#[cfg(test)] +mod tests { + use frame_support::parameter_types; + use hex_literal::hex; + use snowbridge_core::{ + outbound::{v1::Fee, SendError, SendMessageFeeProvider}, + AgentIdOf, + }; + use sp_std::default::Default; + use xcm::{ + latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, + prelude::SendError as XcmSendError, + }; + + use super::*; + + parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + } + + struct MockOkOutboundQueue; + impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Ok(((), Fee { local: 1, remote: 1 })) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } + } + + impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + struct MockErrOutboundQueue; + impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } + } + + impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + + pub struct MockTokenIdConvert; + impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) + } + fn convert_back(_loc: &Location) -> Option { + None + } + } + + #[test] + fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some( + [ + OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, + OnlyChild, + ] + .into(), + ); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_without_global_universal_location_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Kusama), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee, weight_limit: Unlimited }, + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::Unroutable)); + } + + #[test] + fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + + let mut message: Option> = Some( + vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::Unroutable)); + } + + #[test] + fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert!(result.is_ok()); + } + + #[test] + fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) + } + + #[test] + fn xcm_converter_convert_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); + } + + #[test] + fn xcm_converter_convert_without_buy_execution_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); + } + + #[test] + fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); + } + + #[test] + fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); + } + + #[test] + fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + ClearTopic, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); + } + + #[test] + fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); + } + + #[test] + fn xcm_converter_with_different_fee_asset_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = + Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); + } + + #[test] + fn xcm_converter_with_fees_greater_than_reserve_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); + } + + #[test] + fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let message: Xcm<()> = vec![].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); + } + + #[test] + fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ClearError, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); + } + + #[test] + fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); + } + + #[test] + fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); + } + + #[test] + fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee = Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); + } + + #[test] + fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { + let network = BridgedNetwork::get(); + + let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![ + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), + fun: Fungible(1000), + }, + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); + } + + #[test] + fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); + } + + #[test] + fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(0), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }] + .into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 32] = + hex!("2000000000000000000000000000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: [ + GlobalConsensus(Polkadot), + Parachain(1000), + AccountId32 { network: Some(Polkadot), id: beneficiary_address }, + ] + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed( + ) { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { + network: Some(Ethereum { chain_id: 2 }), + key: beneficiary_address, + } + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); + } + + #[test] + fn test_describe_asset_hub() { + let legacy_location: Location = Location::new(0, [Parachain(1000)]); + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: Location = Location::new(1, [Parachain(1000)]); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) + } + + #[test] + fn test_describe_here() { + let location: Location = Location::new(0, []); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) + } + + #[test] + fn xcm_converter_transfer_native_token_success() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); + let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = + Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); + } + + #[test] + fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + // Invalid asset location from a different consensus + let asset_location = Location { + parents: 2, + interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into(), + }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); + } + + #[test] + fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } + + #[test] + fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2.rs b/bridges/snowbridge/primitives/router/src/outbound/v2.rs new file mode 100644 index 000000000000..4476c913aa00 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/v2.rs @@ -0,0 +1,1777 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +use codec::{Decode, Encode}; +use core::slice::Iter; +use sp_std::ops::ControlFlow; + +use frame_support::{ + ensure, + traits::{Contains, Get, ProcessMessageError}, + BoundedVec, +}; +use snowbridge_core::{ + outbound::v2::{Command, Message, SendMessage}, + AgentId, TokenId, TokenIdOf, TokenIdOf as LocationIdOf, +}; +use sp_core::{H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + )>, +); + +impl + ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + > +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, local_sub) = universal_source.clone() + .ok_or_else(|| { + log::error!(target: TARGET, "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'."); + SendError::NotApplicable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let source_location = Location::new(1, local_sub.clone()); + + let agent_id = match AgentHashedDescription::convert_location(&source_location) { + Some(id) => id, + None => { + log::error!(target: TARGET, "unroutable due to not being able to create agent id. '{source_location:?}'"); + return Err(SendError::NotApplicable) + }, + }; + + let message = message.clone().ok_or_else(|| { + log::error!(target: TARGET, "xcm message not provided."); + SendError::MissingArgument + })?; + + // Inspect AliasOrigin as V2 message + let mut instructions = message.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + ensure!(result.is_err(), SendError::NotApplicable); + + let mut converter = + XcmConverter::::new(&message, expected_network, agent_id); + let message = converter.convert().map_err(|err| { + log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + // validate the message + let (ticket, _) = OutboundQueue::validate(&message).map_err(|err| { + log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default())) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: TARGET, "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: TARGET, "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: TARGET, "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// Errors that can be thrown to the pattern matching step. +#[derive(PartialEq, Debug)] +pub enum XcmConverterError { + UnexpectedEndOfXcm, + EndOfXcmMessageExpected, + WithdrawAssetExpected, + DepositAssetExpected, + NoReserveAssets, + FilterDoesNotConsumeAllAssets, + TooManyAssets, + ZeroAssetTransfer, + BeneficiaryResolutionFailed, + AssetResolutionFailed, + InvalidFeeAsset, + SetTopicExpected, + ReserveAssetDepositedExpected, + InvalidAsset, + UnexpectedInstruction, + TooManyCommands, + AliasOriginExpected, + InvalidOrigin, +} + +macro_rules! match_expression { + ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => Some($value), + _ => None, + } + }; +} + +pub struct XcmConverter<'a, ConvertAssetId, Call> { + iter: Peekable>>, + message: Vec>, + ethereum_network: NetworkId, + agent_id: AgentId, + _marker: PhantomData, +} +impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> +where + ConvertAssetId: MaybeEquivalence, +{ + pub fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { + Self { + message: message.clone().inner().into(), + iter: message.inner().iter().peekable(), + ethereum_network, + agent_id, + _marker: Default::default(), + } + } + + pub fn convert(&mut self) -> Result { + let result = match self.jump_to() { + // PNA + Ok(ReserveAssetDeposited { .. }) => self.send_native_tokens_message(), + // ENA + Ok(WithdrawAsset { .. }) => self.send_tokens_message(), + Err(e) => Err(e), + _ => return Err(XcmConverterError::UnexpectedInstruction), + }?; + + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(XcmConverterError::EndOfXcmMessageExpected) + } + + Ok(result) + } + + /// Convert the xcm for Ethereum-native token from AH into the Message which will be executed + /// on Ethereum Gateway contract, we expect an input of the form: + /// # WithdrawAsset(WETH_FEE) + /// # PayFees(WETH_FEE) + /// # WithdrawAsset(ENA) + /// # AliasOrigin(Origin) + /// # DepositAsset(ENA) + /// # SetTopic + fn send_tokens_message(&mut self) -> Result { + use XcmConverterError::*; + + // Get fee amount + let fee_amount = self.extract_remote_fee()?; + + // Get the reserve assets from WithdrawAsset. + let reserve_assets = + match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) + .ok_or(WithdrawAssetExpected)?; + + // Check AliasOrigin. + let origin_loc = match_expression!(self.next()?, AliasOrigin(origin), origin) + .ok_or(AliasOriginExpected)?; + let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // only fungible asset is allowed + let (token, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // ensure SetTopic exists + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + let message = Message { + id: (*topic_id).into(), + origin, + fee: fee_amount, + commands: BoundedVec::try_from(vec![Command::UnlockNativeToken { + agent_id: self.agent_id, + token, + recipient, + amount, + }]) + .map_err(|_| TooManyCommands)?, + }; + + Ok(message) + } + + fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { + self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn network_matches(&self, network: &Option) -> bool { + if let Some(network) = network { + *network == self.ethereum_network + } else { + true + } + } + + /// Convert the xcm for Polkadot-native token from AH into the Message which will be executed + /// on Ethereum Gateway contract, we expect an input of the form: + /// # WithdrawAsset(WETH) + /// # PayFees(WETH) + /// # ReserveAssetDeposited(PNA) + /// # AliasOrigin(Origin) + /// # DepositAsset(PNA) + /// # SetTopic + fn send_native_tokens_message(&mut self) -> Result { + use XcmConverterError::*; + + // Get fee amount + let fee_amount = self.extract_remote_fee()?; + + // Get the reserve assets. + let reserve_assets = + match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) + .ok_or(ReserveAssetDepositedExpected)?; + + // Check AliasOrigin. + let origin_loc = match_expression!(self.next()?, AliasOrigin(origin), origin) + .ok_or(AliasOriginExpected)?; + let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // only fungible asset is allowed + let (asset_id, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // Ensure PNA already registered + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + ensure!(asset_id == expected_asset_id, InvalidAsset); + + // ensure SetTopic exists + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + let message = Message { + origin, + fee: fee_amount, + id: (*topic_id).into(), + commands: BoundedVec::try_from(vec![Command::MintForeignToken { + token_id, + recipient, + amount, + }]) + .map_err(|_| TooManyCommands)?, + }; + + Ok(message) + } + + /// Skip fee instructions and jump to the primary asset instruction + fn jump_to(&mut self) -> Result<&Instruction, XcmConverterError> { + ensure!(self.message.len() > 3, XcmConverterError::UnexpectedEndOfXcm); + self.message.get(2).ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + /// Extract the fee asset item from PayFees(V5) + fn extract_remote_fee(&mut self) -> Result { + use XcmConverterError::*; + let _ = match_expression!(self.next()?, WithdrawAsset(fee), fee) + .ok_or(WithdrawAssetExpected)?; + let fee_asset = + match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; + // Todo: Validate fee asset is WETH + let fee_amount = match fee_asset { + Asset { id: _, fun: Fungible(amount) } => Some(*amount), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + Ok(fee_amount) + } +} + +/// An adapter for the implementation of `ExporterFor`, which attempts to find the +/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm` +/// in the provided `T` table containing various exporters. +pub struct XcmFilterExporter(core::marker::PhantomData<(T, M)>); +impl>> ExporterFor for XcmFilterExporter { + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorLocation, + xcm: &Xcm<()>, + ) -> Option<(Location, Option)> { + // check the XCM + if !M::contains(xcm) { + return None + } + // check `network` and `remote_location` + T::exporter_for(network, remote_location, xcm) + } +} + +/// Xcm for SnowbridgeV2 which requires XCMV5 +pub struct XcmForSnowbridgeV2; +impl Contains> for XcmForSnowbridgeV2 { + fn contains(xcm: &Xcm<()>) -> bool { + let mut instructions = xcm.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + result.is_err() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::parameter_types; + use hex_literal::hex; + use snowbridge_core::{ + outbound::{v2::Fee, SendError, SendMessageFeeProvider}, + AgentIdOf, + }; + use sp_std::default::Default; + use xcm::{ + latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, + prelude::SendError as XcmSendError, + }; + + parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + } + + struct MockOkOutboundQueue; + impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Ok(((), Fee { local: 1 })) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } + } + + impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + struct MockErrOutboundQueue; + impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } + } + + impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + + pub struct MockTokenIdConvert; + impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) + } + fn convert_back(_loc: &Location) -> Option { + None + } + } + + #[test] + fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some( + [ + OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, + OnlyChild, + ] + .into(), + ); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_without_global_universal_location_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Kusama), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, + ExpectAsset(fee.into()), + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + + let mut message: Option> = Some( + vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee.clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert!(result.is_ok()); + } + + #[test] + fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) + } + + #[test] + fn xcm_converter_convert_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert!(result.is_ok()); + } + + #[test] + fn xcm_converter_convert_without_buy_execution_yields_invalid_fee_asset() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); + } + + #[test] + fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::UnlockNativeToken { + agent_id: Default::default(), + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let expected_message = Message { + id: [0; 32].into(), + origin: H256::zero(), + fee: 1000, + commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), + }; + let result = converter.convert(); + assert_eq!(result, Ok(expected_message)); + } + + #[test] + fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset.clone(), weight_limit: Unlimited }, + ExpectAsset(fee_asset.into()), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::UnlockNativeToken { + agent_id: Default::default(), + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let expected_message = Message { + id: [0; 32].into(), + origin: H256::zero(), + fee: 500, + commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), + }; + let result = converter.convert(); + assert_eq!(result, Ok(expected_message)); + } + + #[test] + fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + ClearTopic, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); + } + + #[test] + fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); + } + + #[test] + fn xcm_converter_with_different_fee_asset_succeed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = + Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.is_ok(), true); + } + + #[test] + fn xcm_converter_with_fees_greater_than_reserve_succeed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.is_ok(), true); + } + + #[test] + fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let message: Xcm<()> = vec![].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); + } + + #[test] + fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ClearError, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); + } + + #[test] + fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); + } + + #[test] + fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); + } + + #[test] + fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee = Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); + } + + #[test] + fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { + let network = BridgedNetwork::get(); + + let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![ + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), + fun: Fungible(1000), + }, + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); + } + + #[test] + fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); + } + + #[test] + fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(0), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }] + .into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 32] = + hex!("2000000000000000000000000000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: [ + GlobalConsensus(Polkadot), + Parachain(1000), + AccountId32 { network: Some(Polkadot), id: beneficiary_address }, + ] + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); + } + + #[test] + fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed( + ) { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { + network: Some(Ethereum { chain_id: 2 }), + key: beneficiary_address, + } + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); + } + + #[test] + fn test_describe_asset_hub() { + let legacy_location: Location = Location::new(0, [Parachain(1000)]); + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: Location = Location::new(1, [Parachain(1000)]); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) + } + + #[test] + fn test_describe_here() { + let location: Location = Location::new(0, []); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) + } + + #[test] + fn xcm_converter_transfer_native_token_success() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); + let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); + + let assets: Assets = + vec![Asset { id: AssetId(asset_location.clone()), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + ExpectAsset(Asset { id: AssetId(asset_location), fun: Fungible(amount) }.into()), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = + Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; + let expected_message = Message { + id: [0; 32].into(), + origin: H256::zero(), + fee: 1000000, + commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), + }; + let result = converter.convert(); + assert_eq!(result, Ok(expected_message)); + } + + #[test] + fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + // Invalid asset location from a different consensus + let asset_location = Location { + parents: 2, + interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into(), + }; + + let assets: Assets = + vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); + } + + #[test] + fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } + + #[test] + fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } +} diff --git a/bridges/snowbridge/runtime/runtime-common/src/tests.rs b/bridges/snowbridge/runtime/runtime-common/src/tests.rs index 8d9a88f42933..dea5ad5411c2 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/tests.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/tests.rs @@ -1,6 +1,9 @@ use crate::XcmExportFeeToSibling; use frame_support::{parameter_types, sp_runtime::testing::H256}; -use snowbridge_core::outbound::{Fee, Message, SendError, SendMessage, SendMessageFeeProvider}; +use snowbridge_core::outbound::{ + v1::{Fee, Message, SendMessage}, + SendError, SendMessageFeeProvider, +}; use xcm::prelude::{ Asset, Assets, Here, Kusama, Location, NetworkId, Parachain, XcmContext, XcmError, XcmHash, XcmResult, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index b87f25ac0f01..7f2f42792ec0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -51,3 +51,4 @@ snowbridge-pallet-system = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true } +snowbridge-pallet-outbound-queue-v2 = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 6c1cdb98e8b2..cd826e3bfb29 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -20,6 +20,7 @@ mod claim_assets; mod register_bridged_assets; mod send_xcm; mod snowbridge; +mod snowbridge_v2; mod teleport; mod transact; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e7..6921f0e870f2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -22,7 +22,8 @@ use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ - Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, + v1::{Command, Destination, MessageV1, VersionedMessage}, + EthereumLocationsConverterFor, }; use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; @@ -256,7 +257,6 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); BridgeHubWestend::execute_with(|| { - use bridge_hub_westend_runtime::xcm_config::TreasuryAccount; type RuntimeEvent = ::RuntimeEvent; // Check that the transfer token back to Ethereum message was queue in the Ethereum // Outbound Queue @@ -265,21 +265,12 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] ); let events = BridgeHubWestend::events(); - // Check that the local fee was credited to the Snowbridge sovereign account - assert!( - events.iter().any(|event| matches!( - event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == TreasuryAccount::get().into() && *amount == 5071000000 - )), - "Snowbridge sovereign takes local fee." - ); // Check that the remote fee was credited to the AssetHub sovereign account assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) - if *who == assethub_sovereign && *amount == 2680000000000, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who,.. }) + if *who == assethub_sovereign )), "AssetHub sovereign takes remote fee." ); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs new file mode 100644 index 000000000000..8ded64c512ec --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -0,0 +1,314 @@ +// Copyright (C) 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::imports::*; +use bridge_hub_westend_runtime::EthereumInboundQueue; +use hex_literal::hex; +use snowbridge_core::AssetMetadata; +use snowbridge_router_primitives::inbound::{ + v1::{Command, Destination, MessageV1, VersionedMessage}, + EthereumLocationsConverterFor, +}; +use sp_runtime::MultiAddress; +use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +use xcm::v5::AssetTransferFilter; +use xcm_executor::traits::ConvertLocation; + +const INITIAL_FUND: u128 = 5_000_000_000_000; +pub const CHAIN_ID: u64 = 11155111; +pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); +const XCM_FEE: u128 = 100_000_000_000; +const TOKEN_AMOUNT: u128 = 100_000_000_000; + +#[test] +fn send_weth_from_asset_hub_to_ethereum() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Local fee amount(in DOT) should cover + // 1. execution cost on AH + // 2. delivery cost to BH + // 3. execution cost on BH + let local_fee_amount = 200_000_000_000; + // Remote fee amount(in WETH) should cover execution cost on Ethereum + let remote_fee_amount = 4_000_000_000; + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + let remote_fee_asset = + Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + let reserve_asset = Asset { + id: AssetId(weth_asset_location.clone()), + fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), + }; + let assets = vec![ + Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }, + local_fee_asset.clone(), + ]; + let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]); + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset.clone() }, + InitiateTransfer { + destination, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset.clone().into(), + ))), + preserve_origin: true, + assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( + reserve_asset.clone().into(), + ))], + remote_xcm: xcm_on_bh, + }, + ])); + + // Send the Weth back to Ethereum + ::PolkadotXcm::execute( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + bx!(xcms), + Weight::from(8_000_000_000), + ) + .unwrap(); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + let events = BridgeHubWestend::events(); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn transfer_relay_token() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( + BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), + ); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + let asset_id: Location = Location { parents: 1, interior: [].into() }; + let _expected_asset_id: Location = Location { + parents: 1, + interior: [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))].into(), + }; + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Balances::force_set_balance( + RuntimeOrigin::root(), + sp_runtime::MultiAddress::Id(BridgeHubWestendSender::get()), + INITIAL_FUND * 10, + )); + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(asset_id.clone())), + AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + }); + + // Send token to Ethereum + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type RuntimeEvent = ::RuntimeEvent; + + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }) + .into(); + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + false, + 1, + )); + + assert_ok!(::ForeignAssets::mint( + RuntimeOrigin::signed(assethub_sovereign.clone().into()), + weth_asset_location.clone().try_into().unwrap(), + MultiAddress::Id(AssetHubWestendSender::get()), + TOKEN_AMOUNT, + )); + + // Local fee amount(in DOT) should cover + // 1. execution cost on AH + // 2. delivery cost to BH + // 3. execution cost on BH + let local_fee_amount = 200_000_000_000; + // Remote fee amount(in WETH) should cover execution cost on Ethereum + let remote_fee_amount = 4_000_000_000; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + let remote_fee_asset = + Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + + let assets = vec![ + Asset { + id: AssetId(Location::parent()), + fun: Fungible(TOKEN_AMOUNT + local_fee_amount), + }, + remote_fee_asset.clone(), + ]; + + let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]); + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset.clone() }, + InitiateTransfer { + destination, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset.clone().into(), + ))), + preserve_origin: true, + assets: vec![AssetTransferFilter::ReserveDeposit(Definite( + Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }.into(), + ))], + remote_xcm: xcm_on_bh, + }, + ])); + + // Send DOT to Ethereum + ::PolkadotXcm::execute( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + bx!(xcms), + Weight::from(8_000_000_000), + ) + .unwrap(); + + // Check that the native asset transferred to some reserved account(sovereign of Ethereum) + let events = AssetHubWestend::events(); + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount}) + if *who == ethereum_sovereign.clone() && *amount == TOKEN_AMOUNT, + )), + "native token reserved to Ethereum sovereign account." + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + }); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 88ccd42dff7f..0537a5a41e13 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -496,7 +496,10 @@ pub type XcmRouter = WithUniqueTopic<( // Router which wraps and sends xcm to BridgeHub to be delivered to the Ethereum // GlobalConsensus SovereignPaidRemoteExporter< - bridging::to_ethereum::EthereumNetworkExportTable, + ( + bridging::to_ethereum::EthereumNetworkExportTableV2, + bridging::to_ethereum::EthereumNetworkExportTable, + ), XcmpQueue, UniversalLocation, >, @@ -656,7 +659,9 @@ pub mod bridging { /// Needs to be more than fee calculated from DefaultFeeConfig FeeConfigRecord in snowbridge:parachain/pallets/outbound-queue/src/lib.rs /// Polkadot uses 10 decimals, Kusama,Rococo,Westend 12 decimals. pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; + pub const DefaultBridgeHubEthereumBaseFeeV2: Balance = 100_000_000_000; pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get(); + pub storage BridgeHubEthereumBaseFeeV2: Balance = DefaultBridgeHubEthereumBaseFeeV2::get(); pub SiblingBridgeHubWithEthereumInboundQueueInstance: Location = Location::new( 1, [ @@ -679,6 +684,18 @@ pub mod bridging { ), ]; + pub BridgeTableV2: sp_std::vec::Vec = sp_std::vec![ + NetworkExportTableItem::new( + EthereumNetwork::get(), + Some(sp_std::vec![Junctions::Here]), + SiblingBridgeHub::get(), + Some(( + XcmBridgeHubRouterFeeAssetId::get(), + BridgeHubEthereumBaseFeeV2::get(), + ).into()) + ), + ]; + /// Universal aliases pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( sp_std::vec![ @@ -689,8 +706,18 @@ pub mod bridging { pub EthereumBridgeTable: sp_std::vec::Vec = sp_std::vec::Vec::new().into_iter() .chain(BridgeTable::get()) .collect(); + + pub EthereumBridgeTableV2: sp_std::vec::Vec = sp_std::vec::Vec::new().into_iter() + .chain(BridgeTableV2::get()) + .collect(); } + pub type EthereumNetworkExportTableV2 = + snowbridge_router_primitives::outbound::v2::XcmFilterExporter< + xcm_builder::NetworkExportTable, + snowbridge_router_primitives::outbound::v2::XcmForSnowbridgeV2, + >; + pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable; pub type EthereumAssetFromEthereum = diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 4af8a9f43850..99f82fd8bed4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -112,6 +112,7 @@ snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } +snowbridge-merkle-tree = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } @@ -189,6 +190,7 @@ std = [ "serde_json/std", "snowbridge-beacon-primitives/std", "snowbridge-core/std", + "snowbridge-merkle-tree/std", "snowbridge-outbound-queue-runtime-api/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index be7005b5379a..a405bd5b002b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -24,7 +24,7 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use snowbridge_router_primitives::{inbound::v1::MessageToXcm, outbound::v1::EthereumBlobExporter}; use sp_core::H160; use testnet_parachains_constants::rococo::{ currency::*, @@ -37,6 +37,7 @@ use crate::xcm_config::RelayNetwork; use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; +use snowbridge_core::outbound::v2::DefaultOutboundQueue; use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, FixedU128, @@ -107,7 +108,7 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Decimals = ConstU8<12>; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type GasMeter = snowbridge_core::outbound::v1::ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; @@ -191,6 +192,7 @@ impl snowbridge_pallet_system::Config for Runtime { type InboundDeliveryCost = EthereumInboundQueue; type UniversalLocation = UniversalLocation; type EthereumLocation = EthereumLocation; + type OutboundQueueV2 = DefaultOutboundQueue; } #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index 98e2450ee832..679d33f3456a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -26,6 +26,7 @@ use testnet_parachains_constants::rococo::xcm_version::SAFE_XCM_VERSION; use xcm::latest::WESTEND_GENESIS_HASH; const BRIDGE_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); +use xcm::latest::WESTEND_GENESIS_HASH; fn bridge_hub_rococo_genesis( invulnerables: Vec<(AccountId, AuraId)>, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index ff7af475f5e2..9d24395e8f59 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -92,7 +92,7 @@ pub use sp_runtime::BuildStorage; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use rococo_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; use snowbridge_core::{ - outbound::{Command, Fee}, + outbound::v1::{Command, Fee}, AgentId, PricingParameters, }; use xcm::{latest::prelude::*, prelude::*}; @@ -421,6 +421,7 @@ impl pallet_message_queue::Config for Runtime { RuntimeCall, >, EthereumOutboundQueue, + EthereumOutboundQueue, >; type Size = u32; // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: @@ -1002,7 +1003,7 @@ impl_runtime_apis! { } impl snowbridge_outbound_queue_runtime_api::OutboundQueueApi for Runtime { - fn prove_message(leaf_index: u64) -> Option { + fn prove_message(leaf_index: u64) -> Option { snowbridge_pallet_outbound_queue::api::prove_message::(leaf_index) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 637e7c710640..ed94e442c2f7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -114,6 +114,10 @@ snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } +snowbridge-pallet-inbound-queue-v2 = { workspace = true } +snowbridge-pallet-outbound-queue-v2 = { workspace = true } +snowbridge-outbound-queue-runtime-api-v2 = { workspace = true } +snowbridge-merkle-tree = { workspace = true } [dev-dependencies] @@ -185,9 +189,13 @@ std = [ "serde_json/std", "snowbridge-beacon-primitives/std", "snowbridge-core/std", + "snowbridge-merkle-tree/std", + "snowbridge-outbound-queue-runtime-api-v2/std", "snowbridge-outbound-queue-runtime-api/std", "snowbridge-pallet-ethereum-client/std", + "snowbridge-pallet-inbound-queue-v2/std", "snowbridge-pallet-inbound-queue/std", + "snowbridge-pallet-outbound-queue-v2/std", "snowbridge-pallet-outbound-queue/std", "snowbridge-pallet-system/std", "snowbridge-router-primitives/std", @@ -248,7 +256,9 @@ runtime-benchmarks = [ "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", + "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-outbound-queue/runtime-benchmarks", "snowbridge-pallet-system/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", @@ -288,7 +298,9 @@ try-runtime = [ "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", "snowbridge-pallet-ethereum-client/try-runtime", + "snowbridge-pallet-inbound-queue-v2/try-runtime", "snowbridge-pallet-inbound-queue/try-runtime", + "snowbridge-pallet-outbound-queue-v2/try-runtime", "snowbridge-pallet-outbound-queue/try-runtime", "snowbridge-pallet-system/try-runtime", "sp-runtime/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 94921fd8af9a..e25caed95a02 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -19,13 +19,16 @@ use crate::XcmRouter; use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, - Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, - RuntimeEvent, TransactionByteFee, + Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumOutboundQueueV2, EthereumSystem, + MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use snowbridge_router_primitives::{ + inbound::{v1::MessageToXcm, v2::MessageToXcm as MessageToXcmV2}, + outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, +}; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -55,6 +58,14 @@ pub type SnowbridgeExporter = EthereumBlobExporter< EthereumSystem, >; +pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< + UniversalLocation, + EthereumNetwork, + snowbridge_pallet_outbound_queue_v2::Pallet, + snowbridge_core::AgentIdOf, + EthereumSystem, +>; + // Ethereum Bridge parameter_types! { pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); @@ -102,6 +113,36 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type AssetTransactor = ::AssetTransactor; } +impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_pallet_ethereum_client::Pallet; + type Token = Balances; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type ChannelLookup = EthereumSystem; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type MessageConverter = MessageToXcmV2< + CreateAssetCall, + CreateAssetDeposit, + ConstU8, + AccountId, + Balance, + EthereumSystem, + EthereumUniversalLocation, + AssetHubFromEthereum, + >; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type MaxMessageSize = ConstU32<2048>; + type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type PricingParameters = EthereumSystem; + type AssetTransactor = ::AssetTransactor; +} + impl snowbridge_pallet_outbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Hashing = Keccak256; @@ -109,7 +150,7 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Decimals = ConstU8<12>; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type GasMeter = snowbridge_core::outbound::v1::ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; @@ -117,6 +158,23 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Channels = EthereumSystem; } +impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type MaxMessagePayloadSize = ConstU32<2048>; + type MaxMessagesPerBlock = ConstU32<32>; + type GasMeter = snowbridge_core::outbound::v2::ConstantGasMeter; + type Balance = Balance; + type WeightToFee = WeightToFee; + type Verifier = snowbridge_pallet_ethereum_client::Pallet; + type GatewayAddress = EthereumGatewayAddress; + type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue_v2::WeightInfo; + type RewardLedger = (); + type ConvertAssetId = EthereumSystem; + type EthereumNetwork = EthereumNetwork; +} + #[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] parameter_types! { pub const ChainForkVersions: ForkVersions = ForkVersions { @@ -190,6 +248,7 @@ impl snowbridge_pallet_system::Config for Runtime { type InboundDeliveryCost = EthereumInboundQueue; type UniversalLocation = UniversalLocation; type EthereumLocation = EthereumLocation; + type OutboundQueueV2 = EthereumOutboundQueueV2; } #[cfg(feature = "runtime-benchmarks")] @@ -198,6 +257,7 @@ pub mod benchmark_helpers { use codec::Encode; use snowbridge_beacon_primitives::BeaconHeader; use snowbridge_pallet_inbound_queue::BenchmarkHelper; + use snowbridge_pallet_inbound_queue_v2::BenchmarkHelper as BenchmarkHelperV2; use sp_core::H256; use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; @@ -207,6 +267,12 @@ pub mod benchmark_helpers { } } + impl BenchmarkHelperV2 for Runtime { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { + EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); + } + } + pub struct DoNothingRouter; impl SendXcm for DoNothingRouter { type Ticket = Xcm<()>; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 065400016791..fa9231796b59 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -99,7 +99,7 @@ use parachains_common::{ AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use snowbridge_core::{ - outbound::{Command, Fee}, + outbound::v1::{Command, Fee}, AgentId, PricingParameters, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; @@ -107,6 +107,10 @@ use xcm::VersionedLocation; use westend_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; +use snowbridge_core::outbound::v2::{Fee as FeeV2, InboundMessage}; + +use snowbridge_core::outbound::DryRunError; + /// The address format for describing accounts. pub type Address = MultiAddress; @@ -398,6 +402,7 @@ impl pallet_message_queue::Config for Runtime { RuntimeCall, >, EthereumOutboundQueue, + EthereumOutboundQueueV2, >; type Size = u32; // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: @@ -563,6 +568,8 @@ construct_runtime!( EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, EthereumSystem: snowbridge_pallet_system = 83, + EthereumInboundQueueV2: snowbridge_pallet_inbound_queue_v2 = 84, + EthereumOutboundQueueV2: snowbridge_pallet_outbound_queue_v2 = 85, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -621,6 +628,8 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] + [snowbridge_pallet_inbound_queue_v2, EthereumInboundQueueV2] + [snowbridge_pallet_outbound_queue_v2, EthereumOutboundQueueV2] ); } @@ -890,7 +899,7 @@ impl_runtime_apis! { } impl snowbridge_outbound_queue_runtime_api::OutboundQueueApi for Runtime { - fn prove_message(leaf_index: u64) -> Option { + fn prove_message(leaf_index: u64) -> Option { snowbridge_pallet_outbound_queue::api::prove_message::(leaf_index) } @@ -899,6 +908,15 @@ impl_runtime_apis! { } } + impl snowbridge_outbound_queue_runtime_api_v2::OutboundQueueApiV2 for Runtime { + fn prove_message(leaf_index: u64) -> Option { + snowbridge_pallet_outbound_queue_v2::api::prove_message::(leaf_index) + } + fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,FeeV2),DryRunError> { + snowbridge_pallet_outbound_queue_v2::api::dry_run::(xcm) + } + } + impl snowbridge_system_runtime_api::ControlApi for Runtime { fn agent_id(location: VersionedLocation) -> Option { snowbridge_pallet_system::api::agent_id::(location) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index c1c5c337aca8..cba49ab186c5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -47,7 +47,9 @@ pub mod xcm; pub mod snowbridge_pallet_ethereum_client; pub mod snowbridge_pallet_inbound_queue; +pub mod snowbridge_pallet_inbound_queue_v2; pub mod snowbridge_pallet_outbound_queue; +pub mod snowbridge_pallet_outbound_queue_v2; pub mod snowbridge_pallet_system; pub use block_weights::constants::BlockExecutionWeight; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs new file mode 100644 index 000000000000..7844816f903f --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs @@ -0,0 +1,69 @@ +// Copyright (C) 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. + +//! Autogenerated weights for `snowbridge_pallet_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_inbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `snowbridge_pallet_inbound_queue`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_inbound_queue_v2::WeightInfo for WeightInfo { + /// Storage: EthereumInboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumInboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaders (r:1 w:0) + /// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen) + /// Storage: EthereumInboundQueue Nonce (r:1 w:1) + /// Proof: EthereumInboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn submit() -> Weight { + // Proof Size summary in bytes: + // Measured: `800` + // Estimated: `7200` + // Minimum execution time: 200_000_000 picoseconds. + Weight::from_parts(200_000_000, 0) + .saturating_add(Weight::from_parts(0, 7200)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(6)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue_v2.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue_v2.rs new file mode 100644 index 000000000000..6cde71d9cdec --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue_v2.rs @@ -0,0 +1,98 @@ +// Copyright (C) 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. + +//! Autogenerated weights for `snowbridge_outbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-20, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `192.168.1.13`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// ../target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_outbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --output +// ../parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `snowbridge_outbound_queue`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_outbound_queue_v2::WeightInfo for WeightInfo { + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1) + /// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Nonce (r:1 w:1) + /// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Messages (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured) + fn do_process_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3485` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(39_000_000, 3485) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + fn commit() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(28_000_000, 2579) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn commit_single() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 1586) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn submit_delivery_proof() -> Weight { + // Proof Size summary in bytes: + // Measured: `800` + // Estimated: `7200` + // Minimum execution time: 200_000_000 picoseconds. + Weight::from_parts(200_000_000, 0) + .saturating_add(Weight::from_parts(0, 7200)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(6)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index e692568932fe..856a76792ded 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -210,13 +210,16 @@ impl xcm_executor::Config for XcmConfig { WestendLocation, EthereumNetwork, Self::AssetTransactor, - crate::EthereumOutboundQueue, + crate::EthereumOutboundQueueV2, >, SendXcmFeeToAccount, ), >; - type MessageExporter = - (XcmOverBridgeHubRococo, crate::bridge_to_ethereum_config::SnowbridgeExporter); + type MessageExporter = ( + XcmOverBridgeHubRococo, + crate::bridge_to_ethereum_config::SnowbridgeExporterV2, + crate::bridge_to_ethereum_config::SnowbridgeExporter, + ); type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs index 2f5aa76fbdd7..cdc4c741d863 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs @@ -23,6 +23,7 @@ use frame_support::{ use pallet_message_queue::OnQueueChanged; use scale_info::TypeInfo; use snowbridge_core::ChannelId; +use sp_core::H256; use xcm::latest::prelude::{Junction, Location}; /// The aggregate origin of an inbound message. @@ -44,6 +45,7 @@ pub enum AggregateMessageOrigin { /// /// This is used by Snowbridge inbound queue. Snowbridge(ChannelId), + SnowbridgeV2(H256), } impl From for Location { @@ -55,7 +57,7 @@ impl From for Location { Sibling(id) => Location::new(1, Junction::Parachain(id.into())), // NOTE: We don't need this conversion for Snowbridge. However, we have to // implement it anyway as xcm_builder::ProcessXcmMessage requires it. - Snowbridge(_) => Location::default(), + _ => Location::default(), } } } @@ -82,18 +84,19 @@ impl From for AggregateMessageOrigin { } /// Routes messages to either the XCMP or Snowbridge processor. -pub struct BridgeHubMessageRouter( - PhantomData<(XcmpProcessor, SnowbridgeProcessor)>, +pub struct BridgeHubMessageRouter( + PhantomData<(XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2)>, ) where XcmpProcessor: ProcessMessage, SnowbridgeProcessor: ProcessMessage; -impl ProcessMessage - for BridgeHubMessageRouter +impl ProcessMessage + for BridgeHubMessageRouter where XcmpProcessor: ProcessMessage, SnowbridgeProcessor: ProcessMessage, + SnowbridgeProcessorV2: ProcessMessage, { type Origin = AggregateMessageOrigin; @@ -108,6 +111,7 @@ where Here | Parent | Sibling(_) => XcmpProcessor::process_message(message, origin, meter, id), Snowbridge(_) => SnowbridgeProcessor::process_message(message, origin, meter, id), + SnowbridgeV2(_) => SnowbridgeProcessorV2::process_message(message, origin, meter, id), } } } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 7f50658c4e16..31e7e9fea3a4 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -169,7 +169,6 @@ std = [ "snowbridge-beacon-primitives?/std", "snowbridge-core?/std", "snowbridge-ethereum?/std", - "snowbridge-outbound-queue-merkle-tree?/std", "snowbridge-outbound-queue-runtime-api?/std", "snowbridge-pallet-ethereum-client-fixtures?/std", "snowbridge-pallet-ethereum-client?/std", @@ -541,7 +540,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -1437,11 +1436,6 @@ path = "../bridges/snowbridge/primitives/ethereum" default-features = false optional = true -[dependencies.snowbridge-outbound-queue-merkle-tree] -path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" -default-features = false -optional = true - [dependencies.snowbridge-outbound-queue-runtime-api] path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 2216864fad0f..9591e343bc77 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -1188,10 +1188,6 @@ pub use snowbridge_core; #[cfg(feature = "snowbridge-ethereum")] pub use snowbridge_ethereum; -/// Snowbridge Outbound Queue Merkle Tree. -#[cfg(feature = "snowbridge-outbound-queue-merkle-tree")] -pub use snowbridge_outbound_queue_merkle_tree; - /// Snowbridge Outbound Queue Runtime API. #[cfg(feature = "snowbridge-outbound-queue-runtime-api")] pub use snowbridge_outbound_queue_runtime_api; From 4d6c67841dae588390661854bf578c2e3c0c1733 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 11 Nov 2024 11:57:52 +0800 Subject: [PATCH 02/81] Fix v2 tests --- .../primitives/router/src/outbound/v2.rs | 181 ++++++------------ 1 file changed, 54 insertions(+), 127 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2.rs b/bridges/snowbridge/primitives/router/src/outbound/v2.rs index 4476c913aa00..f1d06fc9662b 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2.rs @@ -918,35 +918,6 @@ mod tests { assert!(result.is_ok()); } - #[test] - fn xcm_converter_convert_without_buy_execution_yields_invalid_fee_asset() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); - } - #[test] fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { let network = BridgedNetwork::get(); @@ -963,53 +934,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::UnlockNativeToken { - agent_id: Default::default(), - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }; - let expected_message = Message { - id: [0; 32].into(), - origin: H256::zero(), - fee: 1000, - commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), - }; - let result = converter.convert(); - assert_eq!(result, Ok(expected_message)); - } - - #[test] - fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ + PayFees { asset: assets.get(0).unwrap().clone() }, WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset.clone(), weight_limit: Unlimited }, - ExpectAsset(fee_asset.into()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1019,20 +946,8 @@ mod tests { .into(); let mut converter = XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::UnlockNativeToken { - agent_id: Default::default(), - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }; - let expected_message = Message { - id: [0; 32].into(), - origin: H256::zero(), - fee: 500, - commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), - }; let result = converter.convert(); - assert_eq!(result, Ok(expected_message)); + assert_eq!(result.is_ok(), true); } #[test] @@ -1051,7 +966,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1101,8 +1018,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1133,8 +1051,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1177,8 +1096,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1239,8 +1159,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), SetTopic([0; 32]), ] .into(); @@ -1269,8 +1190,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, + PayFees { asset: fee.clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1308,8 +1230,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1340,8 +1263,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1372,8 +1296,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1402,9 +1327,10 @@ mod tests { let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: assets.get(0).unwrap().clone() }, WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1436,9 +1362,10 @@ mod tests { let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: assets.get(0).unwrap().clone() }, WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1471,9 +1398,10 @@ mod tests { let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: assets.get(0).unwrap().clone() }, WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1504,17 +1432,14 @@ mod tests { .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: assets.get(0).unwrap().clone() }, WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, - beneficiary: [ - GlobalConsensus(Polkadot), - Parachain(1000), - AccountId32 { network: Some(Polkadot), id: beneficiary_address }, - ] - .into(), + beneficiary: AccountId32 { network: Some(Polkadot), id: beneficiary_address } + .into(), }, SetTopic([0; 32]), ] @@ -1543,8 +1468,9 @@ mod tests { let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + PayFees { asset: assets.get(0).unwrap().clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { @@ -1604,10 +1530,10 @@ mod tests { let filter: AssetFilter = assets.clone().into(); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: assets.get(0).unwrap().clone() }, ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - ExpectAsset(Asset { id: AssetId(asset_location), fun: Fungible(amount) }.into()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), @@ -1621,7 +1547,7 @@ mod tests { Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; let expected_message = Message { id: [0; 32].into(), - origin: H256::zero(), + origin: hex!("aa16eddac8725928eaeda4aae518bf10d02bee80382517d21464a5cdf8d1d8e1").into(), fee: 1000000, commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), }; @@ -1647,9 +1573,10 @@ mod tests { let filter: AssetFilter = assets.clone().into(); let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: assets.get(0).unwrap().clone() }, ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), From a72aa5a57dedecbb499bc672f2264e50faa0d977 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 11 Nov 2024 15:35:49 +0800 Subject: [PATCH 03/81] Fix compile --- .../bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index 679d33f3456a..98e2450ee832 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -26,7 +26,6 @@ use testnet_parachains_constants::rococo::xcm_version::SAFE_XCM_VERSION; use xcm::latest::WESTEND_GENESIS_HASH; const BRIDGE_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); -use xcm::latest::WESTEND_GENESIS_HASH; fn bridge_hub_rococo_genesis( invulnerables: Vec<(AccountId, AuraId)>, From 056fd7f7e3941054a9ac966216203dfdf46e8bfb Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 21:30:32 +0800 Subject: [PATCH 04/81] Rename to OutboundQueueV2Api --- .../snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index 26ab7872ff11..0e5637d388bf 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -11,7 +11,7 @@ use snowbridge_merkle_tree::MerkleProof; use xcm::prelude::Xcm; sp_api::decl_runtime_apis! { - pub trait OutboundQueueApiV2 where Balance: BalanceT + pub trait OutboundQueueV2Api where Balance: BalanceT { /// Generate a merkle proof for a committed message identified by `leaf_index`. /// The merkle root is stored in the block header as a diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index fa9231796b59..929caadf968b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -908,7 +908,7 @@ impl_runtime_apis! { } } - impl snowbridge_outbound_queue_runtime_api_v2::OutboundQueueApiV2 for Runtime { + impl snowbridge_outbound_queue_runtime_api_v2::OutboundQueueV2Api for Runtime { fn prove_message(leaf_index: u64) -> Option { snowbridge_pallet_outbound_queue_v2::api::prove_message::(leaf_index) } From 49837cfabc3509850553e04f4628ca61d88a103e Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 21:37:27 +0800 Subject: [PATCH 05/81] Return raw balance for dry run --- .../pallets/outbound-queue-v2/runtime-api/src/lib.rs | 2 +- bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs | 4 ++-- .../runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index 0e5637d388bf..717f0135af0c 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -18,6 +18,6 @@ sp_api::decl_runtime_apis! { /// `sp_runtime::generic::DigestItem::Other` fn prove_message(leaf_index: u64) -> Option; - fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Fee),DryRunError>; + fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Balance),DryRunError>; } } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index f45e15bad647..7b68b587d4dc 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -33,7 +33,7 @@ where Some(proof) } -pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, Fee), DryRunError> +pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, T::Balance), DryRunError> where T: Config, { @@ -46,7 +46,7 @@ where let message: Message = converter.convert().map_err(|_| DryRunError::ConvertXcmFailed)?; - let fee = Fee::from(crate::Pallet::::calculate_local_fee()); + let fee = crate::Pallet::::calculate_local_fee(); let commands: Vec = message .commands diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 929caadf968b..498bd07530db 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -912,7 +912,7 @@ impl_runtime_apis! { fn prove_message(leaf_index: u64) -> Option { snowbridge_pallet_outbound_queue_v2::api::prove_message::(leaf_index) } - fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,FeeV2),DryRunError> { + fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Balance),DryRunError> { snowbridge_pallet_outbound_queue_v2::api::dry_run::(xcm) } } From 030d95c579ed93be349586bd54b41abee2277233 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 22:02:22 +0800 Subject: [PATCH 06/81] Decode account asap --- .../outbound-queue-v2/runtime-api/src/lib.rs | 5 +--- .../pallets/outbound-queue-v2/src/api.rs | 2 +- .../pallets/outbound-queue-v2/src/envelope.rs | 23 +++++++++++++------ .../pallets/outbound-queue-v2/src/lib.rs | 12 ++++------ 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index 717f0135af0c..08d10ddd9269 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -3,10 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::traits::tokens::Balance as BalanceT; -use snowbridge_core::outbound::{ - v2::{Fee, InboundMessage}, - DryRunError, -}; +use snowbridge_core::outbound::{v2::InboundMessage, DryRunError}; use snowbridge_merkle_tree::MerkleProof; use xcm::prelude::Xcm; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 7b68b587d4dc..15c224931081 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -6,7 +6,7 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_core::{ outbound::{ - v2::{CommandWrapper, Fee, GasMeter, InboundMessage, Message}, + v2::{CommandWrapper, GasMeter, InboundMessage, Message}, DryRunError, }, AgentIdOf, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs index e0f6ba63291c..744c93deb796 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs @@ -5,8 +5,11 @@ use snowbridge_core::inbound::Log; use sp_core::{RuntimeDebug, H160}; use sp_std::prelude::*; +use crate::Config; use alloy_primitives::B256; use alloy_sol_types::{sol, SolEvent}; +use codec::Decode; +use frame_support::pallet_prelude::{Encode, TypeInfo}; sol! { event InboundMessageDispatched(uint64 indexed nonce, bool success, bytes32 indexed reward_address); @@ -14,7 +17,7 @@ sol! { /// An inbound message that has had its outer envelope decoded. #[derive(Clone, RuntimeDebug)] -pub struct Envelope { +pub struct Envelope { /// The address of the outbound queue on Ethereum that emitted this message as an event log pub gateway: H160, /// A nonce for enforcing replay protection and ordering. @@ -22,26 +25,32 @@ pub struct Envelope { /// Delivery status pub success: bool, /// The reward address - pub reward_address: [u8; 32], + pub reward_address: T::AccountId, } -#[derive(Copy, Clone, RuntimeDebug)] -pub struct EnvelopeDecodeError; +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] +pub enum EnvelopeDecodeError { + DecodeLogFailed, + DecodeAccountFailed, +} -impl TryFrom<&Log> for Envelope { +impl TryFrom<&Log> for Envelope { type Error = EnvelopeDecodeError; fn try_from(log: &Log) -> Result { let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); let event = InboundMessageDispatched::decode_log(topics, &log.data, true) - .map_err(|_| EnvelopeDecodeError)?; + .map_err(|_| EnvelopeDecodeError::DecodeLogFailed)?; + + let account = T::AccountId::decode(&mut &event.reward_address[..]) + .map_err(|_| EnvelopeDecodeError::DecodeAccountFailed)?; Ok(Self { gateway: log.address, nonce: event.nonce, success: event.success, - reward_address: event.reward_address.clone().into(), + reward_address: account, }) } } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 43fde9528f5d..2526413f1b3c 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -133,8 +133,6 @@ use alloy_sol_types::SolValue; use alloy_primitives::FixedBytes; -use sp_runtime::traits::TrailingZeroInput; - use sp_runtime::traits::MaybeEquivalence; use xcm::prelude::{Location, NetworkId}; @@ -320,8 +318,8 @@ pub mod pallet { .map_err(|e| Error::::Verification(e))?; // Decode event log into an Envelope - let envelope = - Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + let envelope = Envelope::::try_from(&message.event_log) + .map_err(|_| Error::::InvalidEnvelope)?; // Verify that the message was submitted from the known Gateway contract ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); @@ -330,12 +328,10 @@ pub mod pallet { ensure!(>::contains_key(nonce), Error::::PendingNonceNotExist); let order = >::get(nonce).ok_or(Error::::PendingNonceNotExist)?; - let account = T::AccountId::decode(&mut &envelope.reward_address[..]).unwrap_or( - T::AccountId::decode(&mut TrailingZeroInput::zeroes()).expect("zero address"), - ); + // No fee for governance order if !order.fee.is_zero() { - T::RewardLedger::deposit(account, order.fee.into())?; + T::RewardLedger::deposit(envelope.reward_address, order.fee.into())?; } >::remove(nonce); From 206b3000c7dc4e5641a6298d34ca94c294790e36 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 22:13:54 +0800 Subject: [PATCH 07/81] Revamp comments for V2 --- .../pallets/outbound-queue-v2/src/lib.rs | 54 ++----------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 2526413f1b3c..e74a91cc517f 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -7,14 +7,14 @@ //! Messages come either from sibling parachains via XCM, or BridgeHub itself //! via the `snowbridge-pallet-system`: //! -//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver` -//! 2. `snowbridge_pallet_system::Pallet::send` +//! 1. `snowbridge_router_primitives::outbound::v2::EthereumBlobExporter::deliver` +//! 2. `snowbridge_pallet_system::Pallet::send_v2` //! //! The message submission pipeline works like this: //! 1. The message is first validated via the implementation for -//! [`snowbridge_core::outbound::SendMessage::validate`] +//! [`snowbridge_core::outbound::v2::SendMessage::validate`] //! 2. The message is then enqueued for later processing via the implementation for -//! [`snowbridge_core::outbound::SendMessage::deliver`] +//! [`snowbridge_core::outbound::v2::SendMessage::deliver`] //! 3. The underlying message queue is implemented by [`Config::MessageQueue`] //! 4. The message queue delivers messages back to this pallet via the implementation for //! [`frame_support::traits::ProcessMessage::process_message`] @@ -35,50 +35,6 @@ //! allows us to pause processing of normal user messages while still allowing //! governance commands to be sent to Ethereum. //! -//! # Fees -//! -//! An upfront fee must be paid for delivering a message. This fee covers several -//! components: -//! 1. The weight of processing the message locally -//! 2. The gas refund paid out to relayers for message submission -//! 3. An additional reward paid out to relayers for message submission -//! -//! Messages are weighed to determine the maximum amount of gas they could -//! consume on Ethereum. Using this upper bound, a final fee can be calculated. -//! -//! The fee calculation also requires the following parameters: -//! * Average ETH/DOT exchange rate over some period -//! * Max fee per unit of gas that bridge is willing to refund relayers for -//! -//! By design, it is expected that governance should manually update these -//! parameters every few weeks using the `set_pricing_parameters` extrinsic in the -//! system pallet. -//! -//! This is an interim measure. Once ETH/DOT liquidity pools are available in the Polkadot network, -//! we'll use them as a source of pricing info, subject to certain safeguards. -//! -//! ## Fee Computation Function -//! -//! ```text -//! LocalFee(Message) = WeightToFee(ProcessMessageWeight(Message)) -//! RemoteFee(Message) = MaxGasRequired(Message) * Params.MaxFeePerGas + Params.Reward -//! RemoteFeeAdjusted(Message) = Params.Multiplier * (RemoteFee(Message) / Params.Ratio("ETH/DOT")) -//! Fee(Message) = LocalFee(Message) + RemoteFeeAdjusted(Message) -//! ``` -//! -//! By design, the computed fee includes a safety factor (the `Multiplier`) to cover -//! unfavourable fluctuations in the ETH/DOT exchange rate. -//! -//! ## Fee Settlement -//! -//! On the remote side, in the gateway contract, the relayer accrues -//! -//! ```text -//! Min(GasPrice, Message.MaxFeePerGas) * GasUsed() + Message.Reward -//! ``` -//! Or in plain english, relayers are refunded for gas consumption, using a -//! price that is a minimum of the actual gas price, or `Message.MaxFeePerGas`. -//! //! # Extrinsics //! //! * [`Call::set_operating_mode`]: Set the operating mode @@ -86,7 +42,7 @@ //! # Runtime API //! //! * `prove_message`: Generate a merkle proof for a committed message -//! * `calculate_fee`: Calculate the delivery fee for a message +//! * `dry_run`: Convert xcm to InboundMessage #![cfg_attr(not(feature = "std"), no_std)] pub mod api; pub mod envelope; From 8b3e178211ed65bcc253750df8d1dff1914a3527 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 22:20:30 +0800 Subject: [PATCH 08/81] Custom digest for V2 --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 2 +- .../parachains/runtimes/bridge-hubs/common/src/digest_item.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index e74a91cc517f..293e532bd778 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -309,7 +309,7 @@ pub mod pallet { // Create merkle root of messages let root = merkle_root::<::Hashing, _>(MessageLeaves::::stream_iter()); - let digest_item: DigestItem = CustomDigestItem::Snowbridge(root).into(); + let digest_item: DigestItem = CustomDigestItem::SnowbridgeV2(root).into(); // Insert merkle root into the header digest >::deposit_log(digest_item); diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs index bdfcaedbe82d..5823b15b8d55 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs @@ -24,6 +24,9 @@ pub enum CustomDigestItem { #[codec(index = 0)] /// Merkle root of outbound Snowbridge messages. Snowbridge(H256), + #[codec(index = 1)] + /// Merkle root of outbound Snowbridge V2 messages. + SnowbridgeV2(H256), } /// Convert custom application digest item into a concrete digest item From a22f0ac9f49c89ef50e3b41d3a70ff3505cce4e6 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 22:56:17 +0800 Subject: [PATCH 09/81] Cleanup imports --- .../pallets/outbound-queue-v2/src/lib.rs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 293e532bd778..01725641c7de 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -60,6 +60,8 @@ mod mock; #[cfg(test)] mod test; +use alloy_primitives::FixedBytes; +use alloy_sol_types::SolValue; use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem}; use codec::Decode; use envelope::Envelope; @@ -68,35 +70,23 @@ use frame_support::{ traits::{tokens::Balance, EnqueueMessage, Get, ProcessMessageError}, weights::{Weight, WeightToFee}, }; +pub use pallet::*; use snowbridge_core::{ - inbound::Message as DeliveryMessage, + inbound::{Message as DeliveryMessage, VerificationError, Verifier}, outbound::v2::{CommandWrapper, Fee, GasMeter, InboundMessage, InboundMessageWrapper, Message}, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; -use sp_core::H256; +use sp_core::{H160, H256}; use sp_runtime::{ - traits::{BlockNumberProvider, Hash}, + traits::{BlockNumberProvider, Hash, MaybeEquivalence}, ArithmeticError, DigestItem, }; use sp_std::prelude::*; pub use types::{PendingOrder, ProcessMessageOriginOf}; pub use weights::WeightInfo; - -pub use pallet::*; - -use alloy_sol_types::SolValue; - -use alloy_primitives::FixedBytes; - -use sp_runtime::traits::MaybeEquivalence; - use xcm::prelude::{Location, NetworkId}; -use snowbridge_core::inbound::{VerificationError, Verifier}; - -use sp_core::H160; - #[frame_support::pallet] pub mod pallet { use super::*; From e346bf6860e99928334d66b1f5ca3cef0849a718 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 14 Nov 2024 22:58:27 +0800 Subject: [PATCH 10/81] Update bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs index db1f567e42fc..d16cd031e618 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/types.rs @@ -17,7 +17,7 @@ pub struct PendingOrder { pub nonce: u64, /// The block number in which the message was committed pub block_number: BlockNumber, - /// The fee + /// The fee in Ether provided by the user to incentivize message delivery #[codec(compact)] pub fee: u128, } From 9847bc9759479e6746b97a0292fb6f0d9a369783 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 14 Nov 2024 23:08:40 +0800 Subject: [PATCH 11/81] Clean up with the insert --- .../pallets/outbound-queue-v2/src/lib.rs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 01725641c7de..402aba7af7cc 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -80,7 +80,7 @@ use snowbridge_merkle_tree::merkle_root; use sp_core::{H160, H256}; use sp_runtime::{ traits::{BlockNumberProvider, Hash, MaybeEquivalence}, - ArithmeticError, DigestItem, + DigestItem, }; use sp_std::prelude::*; pub use types::{PendingOrder, ProcessMessageOriginOf}; @@ -357,18 +357,12 @@ pub mod pallet { Messages::::append(Box::new(inbound_message)); MessageLeaves::::append(message_abi_encoded_hash); - >::try_mutate(nonce, |maybe_locked| -> DispatchResult { - let mut locked = maybe_locked.clone().unwrap_or_else(|| PendingOrder { - nonce, - fee: 0, - block_number: frame_system::Pallet::::current_block_number(), - }); - locked.fee = - locked.fee.checked_add(message.fee).ok_or(ArithmeticError::Overflow)?; - *maybe_locked = Some(locked); - Ok(()) - }) - .map_err(|_| Unsupported)?; + let order = PendingOrder { + nonce, + fee: message.fee, + block_number: frame_system::Pallet::::current_block_number(), + }; + >::insert(nonce, order); Nonce::::set(nonce.checked_add(1).ok_or(Unsupported)?); From fb3b30c348dc7c31022324313139ae18b09ef237 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 15 Nov 2024 00:09:54 +0800 Subject: [PATCH 12/81] Remove Fee for V2 --- .../pallets/outbound-queue-v2/src/lib.rs | 2 +- .../src/send_message_impl.rs | 8 +-- bridges/snowbridge/pallets/system/src/lib.rs | 2 +- bridges/snowbridge/pallets/system/src/mock.rs | 31 +++++++++- .../primitives/core/src/outbound/v2.rs | 60 ++----------------- .../primitives/router/src/outbound/v2.rs | 12 ++-- .../src/bridge_to_ethereum_config.rs | 30 +++++++++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 13 ++-- 8 files changed, 82 insertions(+), 76 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 402aba7af7cc..9722e0876b9f 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -73,7 +73,7 @@ use frame_support::{ pub use pallet::*; use snowbridge_core::{ inbound::{Message as DeliveryMessage, VerificationError, Verifier}, - outbound::v2::{CommandWrapper, Fee, GasMeter, InboundMessage, InboundMessageWrapper, Message}, + outbound::v2::{CommandWrapper, GasMeter, InboundMessage, InboundMessageWrapper, Message}, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs index c37cf0dfa530..97188c9c4bc2 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -25,9 +25,9 @@ where { type Ticket = Message; - fn validate( - message: &Message, - ) -> Result<(Self::Ticket, Fee<::Balance>), SendError> { + type Balance = T::Balance; + + fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { // The inner payload should not be too large let payload = message.encode(); ensure!( @@ -35,7 +35,7 @@ where SendError::MessageTooLarge ); - let fee = Fee::from(Self::calculate_local_fee()); + let fee = Self::calculate_local_fee(); Ok((message.clone(), fee)) } diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 52cc28b7de75..8a5b0a6edbf9 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -844,7 +844,7 @@ pub mod pallet { T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; let payment = match pays_fee { - PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee.total())), + PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee)), PaysFee::No => None, }; diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs index f20f8886450f..53ba8e87c140 100644 --- a/bridges/snowbridge/pallets/system/src/mock.rs +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -12,7 +12,11 @@ use xcm_executor::traits::ConvertLocation; use snowbridge_core::{ gwei, meth, - outbound::{v1::ConstantGasMeter, v2::DefaultOutboundQueue}, + outbound::{ + v1::ConstantGasMeter, + v2::{Message, SendMessage}, + SendError as OutboundSendError, SendMessageFeeProvider, + }, sibling_sovereign_account, AgentId, AllowSiblingsOnly, ParaId, PricingParameters, Rewards, }; use sp_runtime::{ @@ -200,6 +204,29 @@ impl BenchmarkHelper for () { } } +pub struct MockOkOutboundQueue; +impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), OutboundSendError> { + Ok(((), 1_u128)) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type OutboundQueue = OutboundQueue; @@ -214,7 +241,7 @@ impl crate::Config for Test { type EthereumLocation = EthereumDestination; #[cfg(feature = "runtime-benchmarks")] type Helper = (); - type OutboundQueueV2 = DefaultOutboundQueue; + type OutboundQueueV2 = MockOkOutboundQueue; } // Build genesis storage according to the mock runtime. diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs index 4443a6ea5297..94015e506285 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! # Outbound V2 primitives -use crate::outbound::{OperatingMode, SendError, SendMessageFeeProvider}; +use crate::outbound::{OperatingMode, SendError}; use alloy_sol_types::sol; use codec::{Decode, Encode}; use frame_support::{pallet_prelude::ConstU32, BoundedVec}; @@ -230,70 +230,20 @@ pub struct Initializer { pub maximum_required_gas: u64, } -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(PartialEq))] -/// Fee for delivering message -pub struct Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - /// Fee to cover cost of processing the message locally - pub local: Balance, -} - -impl Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - pub fn total(&self) -> Balance { - self.local - } -} - -impl From for Fee -where - Balance: BaseArithmetic + Unsigned + Copy, -{ - fn from(local: Balance) -> Self { - Self { local } - } -} - -pub trait SendMessage: SendMessageFeeProvider { +pub trait SendMessage { type Ticket: Clone + Encode + Decode; + type Balance: BaseArithmetic + Unsigned + Copy; + /// Validate an outbound message and return a tuple: /// 1. Ticket for submitting the message /// 2. Delivery fee - fn validate( - message: &Message, - ) -> Result<(Self::Ticket, Fee<::Balance>), SendError>; + fn validate(message: &Message) -> Result<(Self::Ticket, Self::Balance), SendError>; /// Submit the message ticket for eventual delivery to Ethereum fn deliver(ticket: Self::Ticket) -> Result; } -pub struct DefaultOutboundQueue; -impl SendMessage for DefaultOutboundQueue { - type Ticket = (); - - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Ok(((), Fee { local: Default::default() })) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } -} - -impl SendMessageFeeProvider for DefaultOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - Default::default() - } -} - pub trait GasMeter { /// Measures the maximum amount of gas a command payload will require to *dispatch*, NOT /// including validation & verification. diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2.rs b/bridges/snowbridge/primitives/router/src/outbound/v2.rs index f1d06fc9662b..362ab6922979 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2.rs @@ -483,7 +483,7 @@ mod tests { use frame_support::parameter_types; use hex_literal::hex; use snowbridge_core::{ - outbound::{v2::Fee, SendError, SendMessageFeeProvider}, + outbound::{SendError, SendMessageFeeProvider}, AgentIdOf, }; use sp_std::default::Default; @@ -504,8 +504,10 @@ mod tests { impl SendMessage for MockOkOutboundQueue { type Ticket = (); - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Ok(((), Fee { local: 1 })) + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Ok(((), 1_u128)) } fn deliver(_: Self::Ticket) -> Result { @@ -524,7 +526,9 @@ mod tests { impl SendMessage for MockErrOutboundQueue { type Ticket = (); - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { Err(SendError::MessageTooLarge) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index a405bd5b002b..3d208dc68208 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -25,7 +25,7 @@ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_router_primitives::{inbound::v1::MessageToXcm, outbound::v1::EthereumBlobExporter}; -use sp_core::H160; +use sp_core::{H160, H256}; use testnet_parachains_constants::rococo::{ currency::*, fee::WeightToFee, @@ -37,7 +37,10 @@ use crate::xcm_config::RelayNetwork; use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; -use snowbridge_core::outbound::v2::DefaultOutboundQueue; +use snowbridge_core::outbound::{ + v2::{Message, SendMessage}, + SendError, SendMessageFeeProvider, +}; use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, FixedU128, @@ -178,6 +181,29 @@ impl snowbridge_pallet_ethereum_client::Config for Runtime { type WeightInfo = crate::weights::snowbridge_pallet_ethereum_client::WeightInfo; } +pub struct DefaultOutboundQueue; +impl SendMessage for DefaultOutboundQueue { + type Ticket = (); + + type Balance = Balance; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Ok(((), Default::default())) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for DefaultOutboundQueue { + type Balance = Balance; + + fn local_fee() -> Self::Balance { + Default::default() + } +} + impl snowbridge_pallet_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OutboundQueue = EthereumOutboundQueue; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 498bd07530db..70a94e8dd630 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -99,17 +99,16 @@ use parachains_common::{ AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use snowbridge_core::{ - outbound::v1::{Command, Fee}, + outbound::{ + v1::{Command, Fee}, + v2::InboundMessage, + DryRunError, + }, AgentId, PricingParameters, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; -use xcm::VersionedLocation; - use westend_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; - -use snowbridge_core::outbound::v2::{Fee as FeeV2, InboundMessage}; - -use snowbridge_core::outbound::DryRunError; +use xcm::VersionedLocation; /// The address format for describing accounts. pub type Address = MultiAddress; From 83b6ff851625be9c5a520bdf602607c28e4de668 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 15 Nov 2024 01:08:43 +0800 Subject: [PATCH 13/81] Reorgnize InboundMessage to abi module --- .../outbound-queue-v2/runtime-api/src/lib.rs | 2 +- .../pallets/outbound-queue-v2/src/api.rs | 5 +- .../pallets/outbound-queue-v2/src/lib.rs | 5 +- .../pallets/outbound-queue-v2/src/test.rs | 2 +- .../primitives/core/src/outbound/v2.rs | 157 ++++++++++-------- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- 6 files changed, 96 insertions(+), 77 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index 08d10ddd9269..f2c88658c23f 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -3,7 +3,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::traits::tokens::Balance as BalanceT; -use snowbridge_core::outbound::{v2::InboundMessage, DryRunError}; +use snowbridge_core::outbound::{v2::abi::InboundMessage, DryRunError}; use snowbridge_merkle_tree::MerkleProof; use xcm::prelude::Xcm; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 15c224931081..336979b47a36 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -6,7 +6,10 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_core::{ outbound::{ - v2::{CommandWrapper, GasMeter, InboundMessage, Message}, + v2::{ + abi::{CommandWrapper, InboundMessage}, + GasMeter, Message, + }, DryRunError, }, AgentIdOf, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 9722e0876b9f..85745101e2f5 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -73,7 +73,10 @@ use frame_support::{ pub use pallet::*; use snowbridge_core::{ inbound::{Message as DeliveryMessage, VerificationError, Verifier}, - outbound::v2::{CommandWrapper, GasMeter, InboundMessage, InboundMessageWrapper, Message}, + outbound::v2::{ + abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, + GasMeter, Message, + }, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index b4d70e37a9e4..0c1c2868cb90 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -13,7 +13,7 @@ use frame_support::{ use codec::Encode; use snowbridge_core::{ outbound::{ - v2::{primary_governance_origin, Command, InboundMessageWrapper, SendMessage}, + v2::{abi::InboundMessageWrapper, primary_governance_origin, Command, SendMessage}, SendError, }, ChannelId, ParaId, diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs index 94015e506285..59be8767c2f4 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -3,7 +3,6 @@ //! # Outbound V2 primitives use crate::outbound::{OperatingMode, SendError}; -use alloy_sol_types::sol; use codec::{Decode, Encode}; use frame_support::{pallet_prelude::ConstU32, BoundedVec}; use hex_literal::hex; @@ -12,89 +11,103 @@ use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_core::{RuntimeDebug, H160, H256}; use sp_std::{vec, vec::Vec}; +use crate::outbound::v2::abi::{ + CreateAgentParams, MintForeignTokenParams, RegisterForeignTokenParams, SetOperatingModeParams, + UnlockNativeTokenParams, UpgradeParams, +}; use alloy_primitives::{Address, FixedBytes}; use alloy_sol_types::SolValue; -sol! { - struct InboundMessageWrapper { - // origin - bytes32 origin; - // Message nonce - uint64 nonce; - // Commands - CommandWrapper[] commands; - } +pub mod abi { + use super::MAX_COMMANDS; + use alloy_sol_types::sol; + use codec::{Decode, Encode}; + use frame_support::BoundedVec; + use scale_info::TypeInfo; + use sp_core::{ConstU32, RuntimeDebug, H256}; + use sp_std::vec::Vec; - #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)] - struct CommandWrapper { - uint8 kind; - uint64 gas; - bytes payload; - } + sol! { + struct InboundMessageWrapper { + // origin + bytes32 origin; + // Message nonce + uint64 nonce; + // Commands + CommandWrapper[] commands; + } - // Payload for Upgrade - struct UpgradeParams { - // The address of the implementation contract - address implAddress; - // Codehash of the new implementation contract. - bytes32 implCodeHash; - // Parameters used to upgrade storage of the gateway - bytes initParams; - } + #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)] + struct CommandWrapper { + uint8 kind; + uint64 gas; + bytes payload; + } - // Payload for CreateAgent - struct CreateAgentParams { - /// @dev The agent ID of the consensus system - bytes32 agentID; - } + // Payload for Upgrade + struct UpgradeParams { + // The address of the implementation contract + address implAddress; + // Codehash of the new implementation contract. + bytes32 implCodeHash; + // Parameters used to upgrade storage of the gateway + bytes initParams; + } - // Payload for SetOperatingMode instruction - struct SetOperatingModeParams { - /// The new operating mode - uint8 mode; - } + // Payload for CreateAgent + struct CreateAgentParams { + /// @dev The agent ID of the consensus system + bytes32 agentID; + } - // Payload for NativeTokenUnlock instruction - struct UnlockNativeTokenParams { - // Token address - address token; - // Recipient address - address recipient; - // Amount to unlock - uint128 amount; - } + // Payload for SetOperatingMode instruction + struct SetOperatingModeParams { + /// The new operating mode + uint8 mode; + } - // Payload for RegisterForeignToken - struct RegisterForeignTokenParams { - /// @dev The token ID (hash of stable location id of token) - bytes32 foreignTokenID; - /// @dev The name of the token - bytes name; - /// @dev The symbol of the token - bytes symbol; - /// @dev The decimal of the token - uint8 decimals; - } + // Payload for NativeTokenUnlock instruction + struct UnlockNativeTokenParams { + // Token address + address token; + // Recipient address + address recipient; + // Amount to unlock + uint128 amount; + } + + // Payload for RegisterForeignToken + struct RegisterForeignTokenParams { + /// @dev The token ID (hash of stable location id of token) + bytes32 foreignTokenID; + /// @dev The name of the token + bytes name; + /// @dev The symbol of the token + bytes symbol; + /// @dev The decimal of the token + uint8 decimals; + } - // Payload for MintForeignTokenParams instruction - struct MintForeignTokenParams { - // Foreign token ID - bytes32 foreignTokenID; - // Recipient address - address recipient; - // Amount to mint - uint128 amount; + // Payload for MintForeignTokenParams instruction + struct MintForeignTokenParams { + // Foreign token ID + bytes32 foreignTokenID; + // Recipient address + address recipient; + // Amount to mint + uint128 amount; + } } -} -#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] -pub struct InboundMessage { - /// Origin - pub origin: H256, - /// Nonce - pub nonce: u64, - /// Commands - pub commands: BoundedVec>, + #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] + pub struct InboundMessage { + /// Origin + pub origin: H256, + /// Nonce + pub nonce: u64, + /// Commands + pub commands: BoundedVec>, + } } pub const MAX_COMMANDS: u32 = 8; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 70a94e8dd630..0ac65c20a86c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -101,7 +101,7 @@ use parachains_common::{ use snowbridge_core::{ outbound::{ v1::{Command, Fee}, - v2::InboundMessage, + v2::abi::InboundMessage, DryRunError, }, AgentId, PricingParameters, From f08e36ece1160f2a1b459b4a72f49f68c7fcc8ed Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 15 Nov 2024 01:44:24 +0800 Subject: [PATCH 14/81] Seperate XcmConverter --- .../pallets/outbound-queue-v2/src/api.rs | 2 +- .../src/outbound/{v2.rs => v2/convert.rs} | 712 +---------------- .../primitives/router/src/outbound/v2/mod.rs | 718 ++++++++++++++++++ 3 files changed, 727 insertions(+), 705 deletions(-) rename bridges/snowbridge/primitives/router/src/outbound/{v2.rs => v2/convert.rs} (59%) create mode 100644 bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 336979b47a36..754cc59b022e 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -15,7 +15,7 @@ use snowbridge_core::{ AgentIdOf, }; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; -use snowbridge_router_primitives::outbound::v2::XcmConverter; +use snowbridge_router_primitives::outbound::v2::convert::XcmConverter; use sp_core::Get; use sp_std::{default::Default, vec::Vec}; use xcm::{ diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs similarity index 59% rename from bridges/snowbridge/primitives/router/src/outbound/v2.rs rename to bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 362ab6922979..a6a0de1bd75e 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -1,162 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Converts XCM messages into simpler commands that can be processed by the Gateway contract +//! Converts XCM messages into InboundMessage that can be processed by the Gateway contract -use codec::{Decode, Encode}; use core::slice::Iter; -use sp_std::ops::ControlFlow; - -use frame_support::{ - ensure, - traits::{Contains, Get, ProcessMessageError}, - BoundedVec, -}; +use frame_support::{ensure, BoundedVec}; use snowbridge_core::{ - outbound::v2::{Command, Message, SendMessage}, + outbound::v2::{Command, Message}, AgentId, TokenId, TokenIdOf, TokenIdOf as LocationIdOf, }; -use sp_core::{H160, H256}; +use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; use xcm::prelude::*; -use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; -use xcm_executor::traits::{ConvertLocation, ExportXcm}; - -const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; - -pub struct EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, ->( - PhantomData<( - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - )>, -); - -impl - ExportXcm - for EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - > -where - UniversalLocation: Get, - EthereumNetwork: Get, - OutboundQueue: SendMessage, - AgentHashedDescription: ConvertLocation, - ConvertAssetId: MaybeEquivalence, -{ - type Ticket = (Vec, XcmHash); - - fn validate( - network: NetworkId, - _channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - let expected_network = EthereumNetwork::get(); - let universal_location = UniversalLocation::get(); - - if network != expected_network { - log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning destination to avoid modifying the value so subsequent exporters can use it. - let dest = destination.clone().ok_or(SendError::MissingArgument)?; - if dest != Here { - log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, local_sub) = universal_source.clone() - .ok_or_else(|| { - log::error!(target: TARGET, "universal source not provided."); - SendError::MissingArgument - })? - .split_global() - .map_err(|()| { - log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'."); - SendError::NotApplicable - })?; - - if Ok(local_net) != universal_location.global_consensus() { - log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}."); - return Err(SendError::NotApplicable) - } - - let source_location = Location::new(1, local_sub.clone()); - - let agent_id = match AgentHashedDescription::convert_location(&source_location) { - Some(id) => id, - None => { - log::error!(target: TARGET, "unroutable due to not being able to create agent id. '{source_location:?}'"); - return Err(SendError::NotApplicable) - }, - }; - - let message = message.clone().ok_or_else(|| { - log::error!(target: TARGET, "xcm message not provided."); - SendError::MissingArgument - })?; - - // Inspect AliasOrigin as V2 message - let mut instructions = message.clone().0; - let result = instructions.matcher().match_next_inst_while( - |_| true, - |inst| { - return match inst { - AliasOrigin(..) => Err(ProcessMessageError::Yield), - _ => Ok(ControlFlow::Continue(())), - } - }, - ); - ensure!(result.is_err(), SendError::NotApplicable); - - let mut converter = - XcmConverter::::new(&message, expected_network, agent_id); - let message = converter.convert().map_err(|err| { - log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); - SendError::Unroutable - })?; - - // validate the message - let (ticket, _) = OutboundQueue::validate(&message).map_err(|err| { - log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}"); - SendError::Unroutable - })?; - - Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default())) - } - - fn deliver(blob: (Vec, XcmHash)) -> Result { - let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) - .map_err(|_| { - log::trace!(target: TARGET, "undeliverable due to decoding error"); - SendError::NotApplicable - })?; - - let message_id = OutboundQueue::deliver(ticket).map_err(|_| { - log::error!(target: TARGET, "OutboundQueue submit of message failed"); - SendError::Transport("other transport error") - })?; - - log::info!(target: TARGET, "message delivered {message_id:#?}."); - Ok(message_id.into()) - } -} +use xcm_executor::traits::ConvertLocation; /// Errors that can be thrown to the pattern matching step. #[derive(PartialEq, Debug)] @@ -440,455 +296,15 @@ where } } -/// An adapter for the implementation of `ExporterFor`, which attempts to find the -/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm` -/// in the provided `T` table containing various exporters. -pub struct XcmFilterExporter(core::marker::PhantomData<(T, M)>); -impl>> ExporterFor for XcmFilterExporter { - fn exporter_for( - network: &NetworkId, - remote_location: &InteriorLocation, - xcm: &Xcm<()>, - ) -> Option<(Location, Option)> { - // check the XCM - if !M::contains(xcm) { - return None - } - // check `network` and `remote_location` - T::exporter_for(network, remote_location, xcm) - } -} - -/// Xcm for SnowbridgeV2 which requires XCMV5 -pub struct XcmForSnowbridgeV2; -impl Contains> for XcmForSnowbridgeV2 { - fn contains(xcm: &Xcm<()>) -> bool { - let mut instructions = xcm.clone().0; - let result = instructions.matcher().match_next_inst_while( - |_| true, - |inst| { - return match inst { - AliasOrigin(..) => Err(ProcessMessageError::Yield), - _ => Ok(ControlFlow::Continue(())), - } - }, - ); - result.is_err() - } -} - #[cfg(test)] mod tests { use super::*; + use crate::outbound::v2::tests::{BridgedNetwork, MockTokenIdConvert, NonBridgedNetwork}; use frame_support::parameter_types; use hex_literal::hex; - use snowbridge_core::{ - outbound::{SendError, SendMessageFeeProvider}, - AgentIdOf, - }; + use snowbridge_core::AgentIdOf; use sp_std::default::Default; - use xcm::{ - latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, - prelude::SendError as XcmSendError, - }; - - parameter_types! { - const MaxMessageSize: u32 = u32::MAX; - const RelayNetwork: NetworkId = Polkadot; - UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); - const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; - const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; - } - - struct MockOkOutboundQueue; - impl SendMessage for MockOkOutboundQueue { - type Ticket = (); - - type Balance = u128; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { - Ok(((), 1_u128)) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } - } - - impl SendMessageFeeProvider for MockOkOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - struct MockErrOutboundQueue; - impl SendMessage for MockErrOutboundQueue { - type Ticket = (); - - type Balance = u128; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { - Err(SendError::MessageTooLarge) - } - - fn deliver(_: Self::Ticket) -> Result { - Err(SendError::MessageTooLarge) - } - } - - impl SendMessageFeeProvider for MockErrOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - - pub struct MockTokenIdConvert; - impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) - } - fn convert_back(_loc: &Location) -> Option { - None - } - } - - #[test] - fn exporter_validate_with_unknown_network_yields_not_applicable() { - let network = Ethereum { chain_id: 1337 }; - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_invalid_destination_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_x8_destination_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Some( - [ - OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, - OnlyChild, - ] - .into(), - ); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_universal_source_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_without_global_universal_location_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_global_bridge_location_yields_not_applicable() { - let network = NonBridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_remote_universal_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Kusama), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_without_xcm_message_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_max_target_fee_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(fees), - BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, - ExpectAsset(fee.into()), - WithdrawAsset(assets), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } - .into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_unparsable_xcm_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - - let mut message: Option> = Some( - vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_xcm_success_case_1() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee.clone() }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert!(result.is_ok()); - } - - #[test] - fn exporter_deliver_with_submit_failure_yields_unroutable() { - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockErrOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); - assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) - } + use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}; #[test] fn xcm_converter_convert_success() { @@ -1593,116 +1009,4 @@ mod tests { let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); } - - #[test] - fn exporter_validate_with_invalid_dest_does_not_alter_destination() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Parachain(1000).into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(Polkadot), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } - - #[test] - fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Here.into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } } diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs new file mode 100644 index 000000000000..292fede50fe0 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +pub mod convert; +use convert::XcmConverter; + +use codec::{Decode, Encode}; +use frame_support::{ + ensure, + traits::{Contains, Get, ProcessMessageError}, +}; +use snowbridge_core::{outbound::v2::SendMessage, TokenId}; +use sp_core::H256; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*}; +use xcm::prelude::*; +use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + )>, +); + +impl + ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + > +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, local_sub) = universal_source.clone() + .ok_or_else(|| { + log::error!(target: TARGET, "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'."); + SendError::NotApplicable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let source_location = Location::new(1, local_sub.clone()); + + let agent_id = match AgentHashedDescription::convert_location(&source_location) { + Some(id) => id, + None => { + log::error!(target: TARGET, "unroutable due to not being able to create agent id. '{source_location:?}'"); + return Err(SendError::NotApplicable) + }, + }; + + let message = message.clone().ok_or_else(|| { + log::error!(target: TARGET, "xcm message not provided."); + SendError::MissingArgument + })?; + + // Inspect AliasOrigin as V2 message + let mut instructions = message.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + ensure!(result.is_err(), SendError::NotApplicable); + + let mut converter = + XcmConverter::::new(&message, expected_network, agent_id); + let message = converter.convert().map_err(|err| { + log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + // validate the message + let (ticket, _) = OutboundQueue::validate(&message).map_err(|err| { + log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default())) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: TARGET, "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: TARGET, "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: TARGET, "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// An adapter for the implementation of `ExporterFor`, which attempts to find the +/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm` +/// in the provided `T` table containing various exporters. +pub struct XcmFilterExporter(core::marker::PhantomData<(T, M)>); +impl>> ExporterFor for XcmFilterExporter { + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorLocation, + xcm: &Xcm<()>, + ) -> Option<(Location, Option)> { + // check the XCM + if !M::contains(xcm) { + return None + } + // check `network` and `remote_location` + T::exporter_for(network, remote_location, xcm) + } +} + +/// Xcm for SnowbridgeV2 which requires XCMV5 +pub struct XcmForSnowbridgeV2; +impl Contains> for XcmForSnowbridgeV2 { + fn contains(xcm: &Xcm<()>) -> bool { + let mut instructions = xcm.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + result.is_err() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::parameter_types; + use hex_literal::hex; + use snowbridge_core::{ + outbound::{v2::Message, SendError, SendMessageFeeProvider}, + AgentIdOf, + }; + use sp_std::default::Default; + use xcm::{ + latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, + prelude::SendError as XcmSendError, + }; + + parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + pub const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + pub const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + } + + struct MockOkOutboundQueue; + impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Ok(((), 1_u128)) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } + } + + impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + struct MockErrOutboundQueue; + impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } + } + + impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } + } + + pub struct MockTokenIdConvert; + impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) + } + fn convert_back(_loc: &Location) -> Option { + None + } + } + + #[test] + fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some( + [ + OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, + OnlyChild, + ] + .into(), + ); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_without_global_universal_location_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Kusama), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_without_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); + } + + #[test] + fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, + ExpectAsset(fee.into()), + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + + let mut message: Option> = Some( + vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + } + + #[test] + fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee.clone() }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert!(result.is_ok()); + } + + #[test] + fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) + } + + #[test] + fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } + + #[test] + fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, + channel, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut msg_wrapper, + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); + } +} From af928badbe5916376de1896ab64edff6ff3a7e92 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 19 Nov 2024 21:15:08 +0800 Subject: [PATCH 15/81] Support multiple commands in one message --- .../router/src/outbound/v2/convert.rs | 255 +++++++----------- .../primitives/router/src/outbound/v2/mod.rs | 4 +- .../src/tests/snowbridge_v2.rs | 156 +++++++++++ 3 files changed, 252 insertions(+), 163 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index a6a0de1bd75e..9e4766e67c8d 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -48,7 +48,6 @@ macro_rules! match_expression { pub struct XcmConverter<'a, ConvertAssetId, Call> { iter: Peekable>>, - message: Vec>, ethereum_network: NetworkId, agent_id: AgentId, _marker: PhantomData, @@ -59,7 +58,6 @@ where { pub fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { Self { - message: message.clone().inner().into(), iter: message.inner().iter().peekable(), ethereum_network, agent_id, @@ -68,115 +66,18 @@ where } pub fn convert(&mut self) -> Result { - let result = match self.jump_to() { - // PNA - Ok(ReserveAssetDeposited { .. }) => self.send_native_tokens_message(), - // ENA - Ok(WithdrawAsset { .. }) => self.send_tokens_message(), - Err(e) => Err(e), - _ => return Err(XcmConverterError::UnexpectedInstruction), - }?; - - // All xcm instructions must be consumed before exit. - if self.next().is_ok() { - return Err(XcmConverterError::EndOfXcmMessageExpected) - } - + let result = self.to_ethereum_message()?; Ok(result) } - /// Convert the xcm for Ethereum-native token from AH into the Message which will be executed - /// on Ethereum Gateway contract, we expect an input of the form: - /// # WithdrawAsset(WETH_FEE) - /// # PayFees(WETH_FEE) - /// # WithdrawAsset(ENA) - /// # AliasOrigin(Origin) - /// # DepositAsset(ENA) - /// # SetTopic - fn send_tokens_message(&mut self) -> Result { - use XcmConverterError::*; - - // Get fee amount - let fee_amount = self.extract_remote_fee()?; - - // Get the reserve assets from WithdrawAsset. - let reserve_assets = - match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) - .ok_or(WithdrawAssetExpected)?; - - // Check AliasOrigin. - let origin_loc = match_expression!(self.next()?, AliasOrigin(origin), origin) - .ok_or(AliasOriginExpected)?; - let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if reserve_assets.len() == 0 { - return Err(NoReserveAssets) - } - - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - - // only fungible asset is allowed - let (token, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - match inner_location.unpack() { - (0, [AccountKey20 { network, key }]) if self.network_matches(network) => - Some((H160(*key), *amount)), - _ => None, - }, - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - // ensure SetTopic exists - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - let message = Message { - id: (*topic_id).into(), - origin, - fee: fee_amount, - commands: BoundedVec::try_from(vec![Command::UnlockNativeToken { - agent_id: self.agent_id, - token, - recipient, - amount, - }]) - .map_err(|_| TooManyCommands)?, - }; - - Ok(message) - } - fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) } + fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { + self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + fn network_matches(&self, network: &Option) -> bool { if let Some(network) = network { *network == self.ethereum_network @@ -185,31 +86,58 @@ where } } - /// Convert the xcm for Polkadot-native token from AH into the Message which will be executed + /// Extract the fee asset item from PayFees(V5) + fn extract_remote_fee(&mut self) -> Result { + use XcmConverterError::*; + let _ = match_expression!(self.next()?, WithdrawAsset(fee), fee) + .ok_or(WithdrawAssetExpected)?; + let fee_asset = + match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; + // Todo: Validate fee asset is WETH + let fee_amount = match fee_asset { + Asset { id: _, fun: Fungible(amount) } => Some(*amount), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + Ok(fee_amount) + } + + /// Convert the xcm for into the Message which will be executed /// on Ethereum Gateway contract, we expect an input of the form: /// # WithdrawAsset(WETH) /// # PayFees(WETH) - /// # ReserveAssetDeposited(PNA) + /// # ReserveAssetDeposited(PNA) | WithdrawAsset(ENA) /// # AliasOrigin(Origin) - /// # DepositAsset(PNA) + /// # DepositAsset(PNA|ENA) /// # SetTopic - fn send_native_tokens_message(&mut self) -> Result { + fn to_ethereum_message(&mut self) -> Result { use XcmConverterError::*; // Get fee amount let fee_amount = self.extract_remote_fee()?; - // Get the reserve assets. - let reserve_assets = - match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) - .ok_or(ReserveAssetDepositedExpected)?; + // Get ENA reserve asset from WithdrawAsset. + let enas = + match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets); + if enas.is_some() { + let _ = self.next(); + } + // Get PNA reserve asset from ReserveAssetDeposited + let pnas = match_expression!( + self.peek(), + Ok(ReserveAssetDeposited(reserve_assets)), + reserve_assets + ); + if pnas.is_some() { + let _ = self.next(); + } // Check AliasOrigin. let origin_loc = match_expression!(self.next()?, AliasOrigin(origin), origin) .ok_or(AliasOriginExpected)?; let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; - let (deposit_assets, beneficiary) = match_expression!( + let (_, beneficiary) = match_expression!( self.next()?, DepositAsset { assets, beneficiary }, (assets, beneficiary) @@ -226,73 +154,76 @@ where .ok_or(BeneficiaryResolutionFailed)?; // Make sure there are reserved assets. - if reserve_assets.len() == 0 { + if enas.is_none() && pnas.is_none() { return Err(NoReserveAssets) } - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) + let mut commands: Vec = Vec::new(); + + if let Some(enas) = enas { + for ena in enas.clone().inner().iter() { + // only fungible asset is allowed + let (token, amount) = match ena { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) + if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + commands.push(Command::UnlockNativeToken { + agent_id: self.agent_id, + token, + recipient, + amount, + }); + } } - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + if let Some(pnas) = pnas { + for pna in pnas.clone().inner().iter() { + let (asset_id, amount) = match pna { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; - // only fungible asset is allowed - let (asset_id, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - Some((inner_location.clone(), *amount)), - _ => None, - } - .ok_or(AssetResolutionFailed)?; + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); + // Ensure PNA already registered + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + ensure!(asset_id == expected_asset_id, InvalidAsset); - // Ensure PNA already registered - let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; - let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; - ensure!(asset_id == expected_asset_id, InvalidAsset); + commands.push(Command::MintForeignToken { token_id, recipient, amount }); + } + } // ensure SetTopic exists let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; let message = Message { + id: (*topic_id).into(), origin, fee: fee_amount, - id: (*topic_id).into(), - commands: BoundedVec::try_from(vec![Command::MintForeignToken { - token_id, - recipient, - amount, - }]) - .map_err(|_| TooManyCommands)?, + commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?, }; - Ok(message) - } - - /// Skip fee instructions and jump to the primary asset instruction - fn jump_to(&mut self) -> Result<&Instruction, XcmConverterError> { - ensure!(self.message.len() > 3, XcmConverterError::UnexpectedEndOfXcm); - self.message.get(2).ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - /// Extract the fee asset item from PayFees(V5) - fn extract_remote_fee(&mut self) -> Result { - use XcmConverterError::*; - let _ = match_expression!(self.next()?, WithdrawAsset(fee), fee) - .ok_or(WithdrawAssetExpected)?; - let fee_asset = - match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; - // Todo: Validate fee asset is WETH - let fee_amount = match fee_asset { - Asset { id: _, fun: Fungible(amount) } => Some(*amount), - _ => None, + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(EndOfXcmMessageExpected) } - .ok_or(AssetResolutionFailed)?; - Ok(fee_amount) + + Ok(message) } } diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs index 292fede50fe0..939a3090564e 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs @@ -18,7 +18,7 @@ use xcm::prelude::*; use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; use xcm_executor::traits::{ConvertLocation, ExportXcm}; -const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; +pub const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; pub struct EthereumBlobExporter< UniversalLocation, @@ -61,6 +61,8 @@ where destination: &mut Option, message: &mut Option>, ) -> SendResult { + log::debug!(target: TARGET, "message route through bridge {message:?}."); + let expected_network = EthereumNetwork::get(); let universal_location = UniversalLocation::get(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 8ded64c512ec..65b887ebcdc0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -312,3 +312,159 @@ fn transfer_relay_token() { ); }); } + +#[test] +fn send_weth_and_dot_from_asset_hub_to_ethereum() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + // Register WETH on AH + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Register WND on BH + assert_ok!(::Balances::force_set_balance( + RuntimeOrigin::root(), + MultiAddress::Id(BridgeHubWestendSender::get()), + INITIAL_FUND * 10, + )); + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(Location::parent())), + AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + + // Transfer some WETH to AH + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Local fee amount(in DOT) should cover + // 1. execution cost on AH + // 2. delivery cost to BH + // 3. execution cost on BH + let local_fee_amount = 200_000_000_000; + // Remote fee amount(in WETH) should cover execution cost on Ethereum + let remote_fee_amount = 4_000_000_000; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + let remote_fee_asset = + Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + let reserve_asset = Asset { + id: AssetId(weth_asset_location.clone()), + fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), + }; + + let weth_asset = + Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }; + let dot_asset = Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }; + + let assets = vec![weth_asset, dot_asset.clone(), local_fee_asset.clone()]; + let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(All), beneficiary }]); + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset.clone() }, + InitiateTransfer { + destination, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset.clone().into(), + ))), + preserve_origin: true, + assets: vec![ + AssetTransferFilter::ReserveWithdraw(Definite(reserve_asset.clone().into())), + AssetTransferFilter::ReserveDeposit(Definite(dot_asset.into())), + ], + remote_xcm: xcm_on_bh, + }, + ])); + + // Send the Weth back to Ethereum + ::PolkadotXcm::execute( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + bx!(xcms), + Weight::from(8_000_000_000), + ) + .unwrap(); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + let events = BridgeHubWestend::events(); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} From d5ab77ba2f72798a8e1072f96e361a745edd0c8d Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 19 Nov 2024 21:23:55 +0800 Subject: [PATCH 16/81] Rename to InvalidPendingNonce & Cleanup --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 85745101e2f5..a400193f3d27 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -185,7 +185,7 @@ pub mod pallet { /// Invalid Gateway InvalidGateway, /// No pending nonce - PendingNonceNotExist, + InvalidPendingNonce, } /// Messages to be committed in the current block. This storage value is killed in @@ -274,9 +274,8 @@ pub mod pallet { ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); let nonce = envelope.nonce; - ensure!(>::contains_key(nonce), Error::::PendingNonceNotExist); - let order = >::get(nonce).ok_or(Error::::PendingNonceNotExist)?; + let order = >::get(nonce).ok_or(Error::::InvalidPendingNonce)?; // No fee for governance order if !order.fee.is_zero() { From f96a6fc26b144e4f640d53c8c9413321c8d929e1 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 19 Nov 2024 21:25:29 +0800 Subject: [PATCH 17/81] Improve comment --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index a400193f3d27..ceeb13bfe41f 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -184,7 +184,7 @@ pub mod pallet { Verification(VerificationError), /// Invalid Gateway InvalidGateway, - /// No pending nonce + /// Pending nonce does not exist InvalidPendingNonce, } From d4910ea98410a5950778b9920fb77b9d619e20e9 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 20 Nov 2024 09:58:59 +0800 Subject: [PATCH 18/81] Fix breaking tests --- .../router/src/outbound/v2/convert.rs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 9e4766e67c8d..65f8063686a0 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -137,7 +137,7 @@ where .ok_or(AliasOriginExpected)?; let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; - let (_, beneficiary) = match_expression!( + let (deposit_assets, beneficiary) = match_expression!( self.next()?, DepositAsset { assets, beneficiary }, (assets, beneficiary) @@ -161,7 +161,13 @@ where let mut commands: Vec = Vec::new(); if let Some(enas) = enas { + ensure!(enas.len() > 0, NoReserveAssets); for ena in enas.clone().inner().iter() { + // Check the the deposit asset filter matches what was reserved. + if !deposit_assets.matches(ena) { + return Err(FilterDoesNotConsumeAllAssets) + } + // only fungible asset is allowed let (token, amount) = match ena { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => @@ -188,7 +194,14 @@ where } if let Some(pnas) = pnas { + ensure!(pnas.len() > 0, NoReserveAssets); for pna in pnas.clone().inner().iter() { + // Check the the deposit asset filter matches what was reserved. + if !deposit_assets.matches(pna) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // Only fungible is allowed let (asset_id, amount) = match pna { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => Some((inner_location.clone(), *amount)), @@ -231,7 +244,6 @@ where mod tests { use super::*; use crate::outbound::v2::tests::{BridgedNetwork, MockTokenIdConvert, NonBridgedNetwork}; - use frame_support::parameter_types; use hex_literal::hex; use snowbridge_core::AgentIdOf; use sp_std::default::Default; @@ -493,7 +505,7 @@ mod tests { XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); + assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); } #[test] @@ -559,7 +571,7 @@ mod tests { } #[test] - fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { + fn xcm_converter_convert_with_two_assets_yields() { let network = BridgedNetwork::get(); let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); @@ -595,7 +607,7 @@ mod tests { XcmConverter::::new(&message, network, Default::default()); let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); + assert_eq!(result.is_ok(), true); } #[test] From 8e803155c65b256fd5de0c87ff99c674e7b08b2a Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 20 Nov 2024 21:43:31 +0800 Subject: [PATCH 19/81] Remove Inbound-queue V2 completely --- Cargo.lock | 41 -- Cargo.toml | 3 - .../pallets/inbound-queue-v2/Cargo.toml | 93 ----- .../pallets/inbound-queue-v2/README.md | 3 - .../inbound-queue-v2/fixtures/Cargo.toml | 34 -- .../inbound-queue-v2/fixtures/src/lib.rs | 7 - .../fixtures/src/register_token.rs | 97 ----- .../fixtures/src/send_token.rs | 95 ----- .../fixtures/src/send_token_to_penpal.rs | 95 ----- .../inbound-queue-v2/src/benchmarking/mod.rs | 53 --- .../pallets/inbound-queue-v2/src/envelope.rs | 50 --- .../pallets/inbound-queue-v2/src/lib.rs | 378 ------------------ .../pallets/inbound-queue-v2/src/mock.rs | 362 ----------------- .../pallets/inbound-queue-v2/src/test.rs | 245 ------------ .../pallets/inbound-queue-v2/src/weights.rs | 31 -- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 4 - .../src/bridge_to_ethereum_config.rs | 39 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 - .../bridge-hub-westend/src/weights/mod.rs | 1 - .../snowbridge_pallet_inbound_queue_v2.rs | 69 ---- 20 files changed, 1 insertion(+), 1701 deletions(-) delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/README.md delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs delete mode 100755 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs delete mode 100755 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs delete mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs delete mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs diff --git a/Cargo.lock b/Cargo.lock index 8881b6e66d0a..9256c98a3763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2830,7 +2830,6 @@ dependencies = [ "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", - "snowbridge-pallet-inbound-queue-v2", "snowbridge-pallet-outbound-queue 0.2.0", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system 0.2.0", @@ -25031,46 +25030,6 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "snowbridge-pallet-inbound-queue-fixtures-v2" -version = "0.10.0" -dependencies = [ - "hex-literal", - "snowbridge-beacon-primitives 0.2.0", - "snowbridge-core 0.2.0", - "sp-core 28.0.0", - "sp-std 14.0.0", -] - -[[package]] -name = "snowbridge-pallet-inbound-queue-v2" -version = "0.2.0" -dependencies = [ - "alloy-primitives", - "alloy-sol-types", - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", - "hex-literal", - "log", - "pallet-balances 28.0.0", - "parity-scale-codec", - "scale-info", - "serde", - "snowbridge-beacon-primitives 0.2.0", - "snowbridge-core 0.2.0", - "snowbridge-pallet-ethereum-client 0.2.0", - "snowbridge-pallet-inbound-queue-fixtures-v2", - "snowbridge-router-primitives 0.9.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-keyring 31.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0", - "staging-xcm 7.0.0", - "staging-xcm-executor 7.0.0", -] - [[package]] name = "snowbridge-pallet-outbound-queue" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6ca014833691..bce62ba0c72d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,8 +49,6 @@ members = [ "bridges/snowbridge/pallets/ethereum-client", "bridges/snowbridge/pallets/ethereum-client/fixtures", "bridges/snowbridge/pallets/inbound-queue", - "bridges/snowbridge/pallets/inbound-queue-v2", - "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", "bridges/snowbridge/pallets/inbound-queue/fixtures", "bridges/snowbridge/pallets/outbound-queue", "bridges/snowbridge/pallets/outbound-queue-v2", @@ -1234,7 +1232,6 @@ snowbridge-pallet-ethereum-client-fixtures = { path = "bridges/snowbridge/pallet snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-queue", default-features = false } snowbridge-pallet-inbound-queue-fixtures = { path = "bridges/snowbridge/pallets/inbound-queue/fixtures", default-features = false } snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", default-features = false } -snowbridge-pallet-inbound-queue-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } snowbridge-pallet-outbound-queue-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml deleted file mode 100644 index d212b18d2d54..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ /dev/null @@ -1,93 +0,0 @@ -[package] -name = "snowbridge-pallet-inbound-queue-v2" -description = "Snowbridge Inbound Queue Pallet V2" -version = "0.2.0" -authors = ["Snowfork "] -edition.workspace = true -repository.workspace = true -license = "Apache-2.0" -categories = ["cryptography::cryptocurrencies"] - -[lints] -workspace = true - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -serde = { optional = true, workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -hex-literal = { optional = true, workspace = true, default-features = true } -log = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } - -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-balances = { workspace = true } -sp-core = { workspace = true } -sp-std = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } - -xcm = { workspace = true } -xcm-executor = { workspace = true } - -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } -snowbridge-pallet-inbound-queue-fixtures-v2 = { optional = true, workspace = true } - -[dev-dependencies] -frame-benchmarking = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } -hex-literal = { workspace = true, default-features = true } - -[features] -default = ["std"] -std = [ - "alloy-primitives/std", - "alloy-sol-types/std", - "codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-balances/std", - "scale-info/std", - "serde", - "snowbridge-beacon-primitives/std", - "snowbridge-core/std", - "snowbridge-pallet-inbound-queue-fixtures-v2?/std", - "snowbridge-router-primitives/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "xcm-executor/std", - "xcm/std", -] -runtime-benchmarks = [ - "frame-benchmarking", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "hex-literal", - "pallet-balances/runtime-benchmarks", - "snowbridge-core/runtime-benchmarks", - "snowbridge-pallet-ethereum-client/runtime-benchmarks", - "snowbridge-pallet-inbound-queue-fixtures-v2/runtime-benchmarks", - "snowbridge-router-primitives/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-balances/try-runtime", - "snowbridge-pallet-ethereum-client/try-runtime", - "sp-runtime/try-runtime", -] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/README.md deleted file mode 100644 index cc2f7c636e68..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ethereum Inbound Queue - -Reads messages from Ethereum and sends it to intended destination on Polkadot, using XCM. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml deleted file mode 100644 index ea30fdddb553..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "snowbridge-pallet-inbound-queue-fixtures-v2" -description = "Snowbridge Inbound Queue Test Fixtures V2" -version = "0.10.0" -authors = ["Snowfork "] -edition.workspace = true -repository.workspace = true -license = "Apache-2.0" -categories = ["cryptography::cryptocurrencies"] - -[lints] -workspace = true - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -hex-literal = { workspace = true, default-features = true } -sp-core = { workspace = true } -sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } - -[features] -default = ["std"] -std = [ - "snowbridge-beacon-primitives/std", - "snowbridge-core/std", - "sp-core/std", - "sp-std/std", -] -runtime-benchmarks = [ - "snowbridge-core/runtime-benchmarks", -] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs deleted file mode 100644 index 00adcdfa186a..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -#![cfg_attr(not(feature = "std"), no_std)] - -pub mod register_token; -pub mod send_token; -pub mod send_token_to_penpal; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs deleted file mode 100644 index 340b2fadfacf..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -// Generated, do not edit! -// See ethereum client README.md for instructions to generate - -use hex_literal::hex; -use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, -}; -use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; -use sp_core::U256; -use sp_std::vec; - -pub fn make_register_token_message() -> InboundQueueFixture { - InboundQueueFixture { - message: Message { - event_log: Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), - }, - proof: Proof { - receipt_proof: (vec![ - hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").to_vec(), - hex!("4a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f").to_vec(), - ], vec![ - hex!("f851a09c01dd6d2d8de951c45af23d3ad00829ce021c04d6c8acbe1612d456ee320d4980808080808080a04a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f8080808080808080").to_vec(), - hex!("f9028c30b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(), - ]), - execution_proof: ExecutionProof { - header: BeaconHeader { - slot: 393, - proposer_index: 4, - parent_root: hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), - state_root: hex!("b62ac34a8cb82497be9542fe2114410c9f6021855b766015406101a1f3d86434").into(), - body_root: hex!("04005fe231e11a5b7b1580cb73b177ae8b338bedd745497e6bb7122126a806db").into(), - }, - ancestry_proof: Some(AncestryProof { - header_branch: vec![ - hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), - hex!("fa84cc88ca53a72181599ff4eb07d8b444bce023fe2347c3b4f51004c43439d3").into(), - hex!("cadc8ae211c6f2221c9138e829249adf902419c78eb4727a150baa4d9a02cc9d").into(), - hex!("33a89962df08a35c52bd7e1d887cd71fa7803e68787d05c714036f6edf75947c").into(), - hex!("2c9760fce5c2829ef3f25595a703c21eb22d0186ce223295556ed5da663a82cf").into(), - hex!("e1aa87654db79c8a0ecd6c89726bb662fcb1684badaef5cd5256f479e3c622e1").into(), - hex!("aa70d5f314e4a1fbb9c362f3db79b21bf68b328887248651fbd29fc501d0ca97").into(), - hex!("160b6c235b3a1ed4ef5f80b03ee1c76f7bf3f591c92fca9d8663e9221b9f9f0f").into(), - hex!("f68d7dcd6a07a18e9de7b5d2aa1980eb962e11d7dcb584c96e81a7635c8d2535").into(), - hex!("1d5f912dfd6697110dd1ecb5cb8e77952eef57d85deb373572572df62bb157fc").into(), - hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), - hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), - hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), - ], - finalized_block_root: hex!("751414cd97c0624f922b3e80285e9f776b08fa22fd5f87391f2ed7ef571a8d46").into(), - }), - execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { - parent_hash: hex!("8092290aa21b7751576440f77edd02a94058429ce50e63a92d620951fb25eda2").into(), - fee_recipient: hex!("0000000000000000000000000000000000000000").into(), - state_root: hex!("96a83e9ddf745346fafcb0b03d57314623df669ed543c110662b21302a0fae8b").into(), - receipts_root: hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").into(), - logs_bloom: hex!("00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000400000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000080000000000000000000000000000040004000000000000002002002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000080000000000000000000000000000000000100000000000000000200000200000010").into(), - prev_randao: hex!("62e309d4f5119d1f5c783abc20fc1a549efbab546d8d0b25ff1cfd58be524e67").into(), - block_number: 393, - gas_limit: 54492273, - gas_used: 199644, - timestamp: 1710552813, - extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), - base_fee_per_gas: U256::from(7u64), - block_hash: hex!("6a9810efb9581d30c1a5c9074f27c68ea779a8c1ae31c213241df16225f4e131").into(), - transactions_root: hex!("2cfa6ed7327e8807c7973516c5c32a68ef2459e586e8067e113d081c3bd8c07d").into(), - withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), - blob_gas_used: 0, - excess_blob_gas: 0, - }), - execution_branch: vec![ - hex!("a6833fa629f3286b6916c6e50b8bf089fc9126bee6f64d0413b4e59c1265834d").into(), - hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), - hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), - hex!("d3af7c05c516726be7505239e0b9c7cb53d24abce6b91cdb3b3995f0164a75da").into(), - ], - } - }, - }, - finalized_header: BeaconHeader { - slot: 864, - proposer_index: 4, - parent_root: hex!("614e7672f991ac268cd841055973f55e1e42228831a211adef207bb7329be614").into(), - state_root: hex!("5fa8dfca3d760e4242ab46d529144627aa85348a19173b6e081172c701197a4a").into(), - body_root: hex!("0f34c083b1803666bb1ac5e73fa71582731a2cf37d279ff0a3b0cad5a2ff371e").into(), - }, - block_roots_root: hex!("b9aab9c388c4e4fcd899b71f62c498fc73406e38e8eb14aa440e9affa06f2a10").into(), - } -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs deleted file mode 100755 index 4075febab59d..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -// Generated, do not edit! -// See ethereum client README.md for instructions to generate - -use hex_literal::hex; -use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, -}; -use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; -use sp_core::U256; -use sp_std::vec; - -pub fn make_send_token_message() -> InboundQueueFixture { - InboundQueueFixture { - message: Message { - event_log: Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - hex!("c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26f").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").into(), - }, - proof: Proof { - receipt_proof: (vec![ - hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").to_vec(), - ], vec![ - hex!("f90451822080b9044b02f90447018301bcb6b9010000800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000f9033cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26fb8c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").to_vec(), - ]), - execution_proof: ExecutionProof { - header: BeaconHeader { - slot: 2321, - proposer_index: 5, - parent_root: hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), - state_root: hex!("d962981467920bb2b7efa4a7a1baf64745582c3250857f49a957c5dae9a0da39").into(), - body_root: hex!("18e3f7f51a350f371ad35d166f2683b42af51d1836b295e4093be08acb0dcb7a").into(), - }, - ancestry_proof: Some(AncestryProof { - header_branch: vec![ - hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), - hex!("48b2e2f5256906a564e5058698f70e3406765fefd6a2edc064bb5fb88aa2ed0a").into(), - hex!("e5ed7c704e845418219b2fda42cd2f3438ffbe4c4b320935ae49439c6189f7a7").into(), - hex!("4a7ce24526b3f571548ad69679e4e260653a1b3b911a344e7f988f25a5c917a7").into(), - hex!("46fc859727ab0d0e8c344011f7d7a4426ccb537bb51363397e56cc7153f56391").into(), - hex!("f496b6f85a7c6c28a9048f2153550a7c5bcb4b23844ed3b87f6baa646124d8a3").into(), - hex!("7318644e474beb46e595a1875acc7444b937f5208065241911d2a71ac50c2de3").into(), - hex!("5cf48519e518ac64286aef5391319782dd38831d5dcc960578a6b9746d5f8cee").into(), - hex!("efb3e50fa39ca9fe7f76adbfa36fa8451ec2fd5d07b22aaf822137c04cf95a76").into(), - hex!("2206cd50750355ffaef4a67634c21168f2b564c58ffd04f33b0dc7af7dab3291").into(), - hex!("1a4014f6c4fcce9949fba74cb0f9e88df086706f9e05560cc9f0926f8c90e373").into(), - hex!("2df7cc0bcf3060be4132c63da7599c2600d9bbadf37ab001f15629bc2255698e").into(), - hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), - ], - finalized_block_root: hex!("f869dd1c9598043008a3ac2a5d91b3d6c7b0bb3295b3843bc84c083d70b0e604").into(), - }), - execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { - parent_hash: hex!("5d7859883dde1eba6c98b20eac18426134b25da2a89e5e360f3343b15e0e0a31").into(), - fee_recipient: hex!("0000000000000000000000000000000000000000").into(), - state_root: hex!("f8fbebed4c84d46231bd293bb9fbc9340d5c28c284d99fdaddb77238b8960ae2").into(), - receipts_root: hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").into(), - logs_bloom: hex!("00800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000").into(), - prev_randao: hex!("15533eeb366c6386bea5aeb8f425871928348c092209e4377f2418a6dedd7fd0").into(), - block_number: 2321, - gas_limit: 30000000, - gas_used: 113846, - timestamp: 1710554741, - extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), - base_fee_per_gas: U256::from(7u64), - block_hash: hex!("585a07122a30339b03b6481eae67c2d3de2b6b64f9f426230986519bf0f1bdfe").into(), - transactions_root: hex!("09cd60ee2207d804397c81f7b7e1e5d3307712b136e5376623a80317a4bdcd7a").into(), - withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), - blob_gas_used: 0, - excess_blob_gas: 0, - }), - execution_branch: vec![ - hex!("9d419471a9a4719b40e7607781fbe32d9a7766b79805505c78c0c58133496ba2").into(), - hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), - hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), - hex!("bee375b8f1bbe4cd0e783c78026c1829ae72741c2dead5cab05d6834c5e5df65").into(), - ], - } - }, - }, - finalized_header: BeaconHeader { - slot: 4032, - proposer_index: 5, - parent_root: hex!("180aaaec59d38c3860e8af203f01f41c9bc41665f4d17916567c80f6cd23e8a2").into(), - state_root: hex!("3341790429ed3bf894cafa3004351d0b99e08baf6c38eb2a54d58e69fd2d19c6").into(), - body_root: hex!("a221e0c695ac7b7d04ce39b28b954d8a682ecd57961d81b44783527c6295f455").into(), - }, - block_roots_root: hex!("5744385ef06f82e67606f49aa29cd162f2e837a68fb7bd82f1fc6155d9f8640f").into(), - } -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs deleted file mode 100755 index 6a951b568ae5..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -// Generated, do not edit! -// See ethereum client README.md for instructions to generate - -use hex_literal::hex; -use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, -}; -use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; -use sp_core::U256; -use sp_std::vec; - -pub fn make_send_token_to_penpal_message() -> InboundQueueFixture { - InboundQueueFixture { - message: Message { - event_log: Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - hex!("be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aa").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").into(), - }, - proof: Proof { - receipt_proof: (vec![ - hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").to_vec(), - ], vec![ - hex!("f90471822080b9046b02f904670183017d9cb9010000800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000f9035cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000007d0b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000201cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cf9015c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aab8e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").to_vec(), - ]), - execution_proof: ExecutionProof { - header: BeaconHeader { - slot: 4235, - proposer_index: 4, - parent_root: hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), - state_root: hex!("725f51771a0ecf72c647a283ab814ca088f998eb8c203181496b0b8e01f624fa").into(), - body_root: hex!("6f1c326d192e7e97e21e27b16fd7f000b8fa09b435ff028849927e382302b0ce").into(), - }, - ancestry_proof: Some(AncestryProof { - header_branch: vec![ - hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), - hex!("335eb186c077fa7053ec96dcc5d34502c997713d2d5bc4eb74842118d8cd5a64").into(), - hex!("326607faf2a7dfc9cfc4b6895f8f3d92a659552deb2c8fd1e892ec00c86c734c").into(), - hex!("4e20002125d7b6504df7c774f3f48e018e1e6762d03489149670a8335bba1425").into(), - hex!("e76af5cd61aade5aec8282b6f1df9046efa756b0466bba5e49032410f7739a1b").into(), - hex!("ee4dcd9527712116380cddafd120484a3bedf867225bbb86850b84decf6da730").into(), - hex!("e4687a07421d3150439a2cd2f09f3b468145d75b359a2e5fa88dfbec51725b15").into(), - hex!("38eaa78978e95759aa9b6f8504a8dbe36151f20ae41907e6a1ea165700ceefcd").into(), - hex!("1c1b071ec6f13e15c47d07d1bfbcc9135d6a6c819e68e7e6078a2007418c1a23").into(), - hex!("0b3ad7ad193c691c8c4ba1606ad2a90482cd1d033c7db58cfe739d0e20431e9e").into(), - hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), - hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), - hex!("b2ffec5f2c14640305dd941330f09216c53b99d198e93735a400a6d3a4de191f").into(), - ], - finalized_block_root: hex!("08be7a59e947f08cd95c4ef470758730bf9e3b0db0824cb663ea541c39b0e65c").into(), - }), - execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { - parent_hash: hex!("5d1186ae041f58785edb2f01248e95832f2e5e5d6c4eb8f7ff2f58980bfc2de9").into(), - fee_recipient: hex!("0000000000000000000000000000000000000000").into(), - state_root: hex!("2a66114d20e93082c8e9b47c8d401a937013487d757c9c2f3123cf43dc1f656d").into(), - receipts_root: hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").into(), - logs_bloom: hex!("00800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000").into(), - prev_randao: hex!("92e063c7e369b74149fdd1d7132ed2f635a19b9d8bff57637b8ee4736576426e").into(), - block_number: 4235, - gas_limit: 30000000, - gas_used: 97692, - timestamp: 1710556655, - extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), - base_fee_per_gas: U256::from(7u64), - block_hash: hex!("ce24fe3047aa20a8f222cd1d04567c12b39455400d681141962c2130e690953f").into(), - transactions_root: hex!("0c8388731de94771777c60d452077065354d90d6e5088db61fc6a134684195cc").into(), - withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), - blob_gas_used: 0, - excess_blob_gas: 0, - }), - execution_branch: vec![ - hex!("99d397fa180078e66cd3a3b77bcb07553052f4e21d447167f3a406f663b14e6a").into(), - hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), - hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), - hex!("53ddf17147819c1abb918178b0230d965d1bc2c0d389f45e91e54cb1d2d468aa").into(), - ], - } - }, - }, - finalized_header: BeaconHeader { - slot: 4672, - proposer_index: 4, - parent_root: hex!("951233bf9f4bddfb2fa8f54e3bd0c7883779ef850e13e076baae3130dd7732db").into(), - state_root: hex!("4d303003b8cb097cbcc14b0f551ee70dac42de2c1cc2f4acfca7058ca9713291").into(), - body_root: hex!("664d13952b6f369bf4cf3af74d067ec33616eb57ed3a8a403fd5bae4fbf737dd").into(), - }, - block_roots_root: hex!("af71048297c070e6539cf3b9b90ae07d86d363454606bc239734629e6b49b983").into(), - } -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs deleted file mode 100644 index 52461a8a7fbe..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -use super::*; - -use crate::Pallet as InboundQueue; -use frame_benchmarking::v2::*; -use frame_support::assert_ok; -use frame_system::RawOrigin; -use snowbridge_pallet_inbound_queue_fixtures_v2::register_token::make_register_token_message; - -#[benchmarks] -mod benchmarks { - use super::*; - - #[benchmark] - fn submit() -> Result<(), BenchmarkError> { - let caller: T::AccountId = whitelisted_caller(); - - let create_message = make_register_token_message(); - - T::Helper::initialize_storage( - create_message.finalized_header, - create_message.block_roots_root, - ); - - let sovereign_account = sibling_sovereign_account::(1000u32.into()); - - let minimum_balance = T::Token::minimum_balance(); - - // So that the receiving account exists - assert_ok!(T::Token::mint_into(&caller, minimum_balance)); - // Fund the sovereign account (parachain sovereign account) so it can transfer a reward - // fee to the caller account - assert_ok!(T::Token::mint_into( - &sovereign_account, - 3_000_000_000_000u128 - .try_into() - .unwrap_or_else(|_| panic!("unable to cast sovereign account balance")), - )); - - #[block] - { - assert_ok!(InboundQueue::::submit( - RawOrigin::Signed(caller.clone()).into(), - create_message.message, - )); - } - - Ok(()) - } - - impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs deleted file mode 100644 index 31a8992442d8..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -use snowbridge_core::{inbound::Log, ChannelId}; - -use sp_core::{RuntimeDebug, H160, H256}; -use sp_std::prelude::*; - -use alloy_primitives::B256; -use alloy_sol_types::{sol, SolEvent}; - -sol! { - event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); -} - -/// An inbound message that has had its outer envelope decoded. -#[derive(Clone, RuntimeDebug)] -pub struct Envelope { - /// The address of the outbound queue on Ethereum that emitted this message as an event log - pub gateway: H160, - /// The message Channel - pub channel_id: ChannelId, - /// A nonce for enforcing replay protection and ordering. - pub nonce: u64, - /// An id for tracing the message on its route (has no role in bridge consensus) - pub message_id: H256, - /// The inner payload generated from the source application. - pub payload: Vec, -} - -#[derive(Copy, Clone, RuntimeDebug)] -pub struct EnvelopeDecodeError; - -impl TryFrom<&Log> for Envelope { - type Error = EnvelopeDecodeError; - - fn try_from(log: &Log) -> Result { - let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - - let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) - .map_err(|_| EnvelopeDecodeError)?; - - Ok(Self { - gateway: log.address, - channel_id: ChannelId::from(event.channel_id.as_ref()), - nonce: event.nonce, - message_id: H256::from(event.message_id.as_ref()), - payload: event.payload, - }) - } -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs deleted file mode 100644 index c26859dcf5d7..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ /dev/null @@ -1,378 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Inbound Queue -//! -//! # Overview -//! -//! Receives messages emitted by the Gateway contract on Ethereum, whereupon they are verified, -//! translated to XCM, and finally sent to their final destination parachain. -//! -//! The message relayers are rewarded using native currency from the sovereign account of the -//! destination parachain. -//! -//! # Extrinsics -//! -//! ## Governance -//! -//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable -//! processing of inbound messages. -//! -//! ## Message Submission -//! -//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination -//! parachain. -#![cfg_attr(not(feature = "std"), no_std)] - -mod envelope; - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; - -pub mod weights; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod test; - -use codec::{Decode, DecodeAll, Encode}; -use envelope::Envelope; -use frame_support::{ - traits::{ - fungible::{Inspect, Mutate}, - tokens::{Fortitude, Preservation}, - }, - weights::WeightToFee, - PalletError, -}; -use frame_system::ensure_signed; -use scale_info::TypeInfo; -use sp_core::H160; -use sp_runtime::traits::Zero; -use sp_std::vec; -use xcm::prelude::{ - send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, Xcm, XcmContext, XcmHash, -}; -use xcm_executor::traits::TransactAsset; - -use snowbridge_core::{ - inbound::{Message, VerificationError, Verifier}, - sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, - StaticLookup, -}; -use snowbridge_router_primitives::inbound::v2::{ - ConvertMessage, ConvertMessageError, VersionedMessage, -}; -use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; - -pub use weights::WeightInfo; - -#[cfg(feature = "runtime-benchmarks")] -use snowbridge_beacon_primitives::BeaconHeader; - -type BalanceOf = - <::Token as Inspect<::AccountId>>::Balance; - -pub use pallet::*; - -pub const LOG_TARGET: &str = "snowbridge-inbound-queue"; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use sp_core::H256; - - #[pallet::pallet] - pub struct Pallet(_); - - #[cfg(feature = "runtime-benchmarks")] - pub trait BenchmarkHelper { - fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256); - } - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The verifier for inbound messages from Ethereum - type Verifier: Verifier; - - /// Message relayers are rewarded with this asset - type Token: Mutate + Inspect; - - /// XCM message sender - type XcmSender: SendXcm; - - // Address of the Gateway contract - #[pallet::constant] - type GatewayAddress: Get; - - /// Convert inbound message to XCM - type MessageConverter: ConvertMessage< - AccountId = Self::AccountId, - Balance = BalanceOf, - >; - - /// Lookup a channel descriptor - type ChannelLookup: StaticLookup; - - /// Lookup pricing parameters - type PricingParameters: Get>>; - - type WeightInfo: WeightInfo; - - #[cfg(feature = "runtime-benchmarks")] - type Helper: BenchmarkHelper; - - /// Convert a weight value into deductible balance type. - type WeightToFee: WeightToFee>; - - /// Convert a length value into deductible balance type - type LengthToFee: WeightToFee>; - - /// The upper limit here only used to estimate delivery cost - type MaxMessageSize: Get; - - /// To withdraw and deposit an asset. - type AssetTransactor: TransactAsset; - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A message was received from Ethereum - MessageReceived { - /// The message channel - channel_id: ChannelId, - /// The message nonce - nonce: u64, - /// ID of the XCM message which was forwarded to the final destination parachain - message_id: [u8; 32], - /// Fee burned for the teleport - fee_burned: BalanceOf, - }, - /// Set OperatingMode - OperatingModeChanged { mode: BasicOperatingMode }, - } - - #[pallet::error] - pub enum Error { - /// Message came from an invalid outbound channel on the Ethereum side. - InvalidGateway, - /// Message has an invalid envelope. - InvalidEnvelope, - /// Message has an unexpected nonce. - InvalidNonce, - /// Message has an invalid payload. - InvalidPayload, - /// Message channel is invalid - InvalidChannel, - /// The max nonce for the type has been reached - MaxNonceReached, - /// Cannot convert location - InvalidAccountConversion, - /// Pallet is halted - Halted, - /// Message verification error, - Verification(VerificationError), - /// XCMP send failure - Send(SendError), - /// Message conversion error - ConvertMessage(ConvertMessageError), - } - - #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] - pub enum SendError { - NotApplicable, - NotRoutable, - Transport, - DestinationUnsupported, - ExceedsMaxMessageSize, - MissingArgument, - Fees, - } - - impl From for Error { - fn from(e: XcmpSendError) -> Self { - match e { - XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), - XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), - XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), - XcmpSendError::DestinationUnsupported => - Error::::Send(SendError::DestinationUnsupported), - XcmpSendError::ExceedsMaxMessageSize => - Error::::Send(SendError::ExceedsMaxMessageSize), - XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), - XcmpSendError::Fees => Error::::Send(SendError::Fees), - } - } - } - - /// The current nonce for each channel - #[pallet::storage] - pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; - - /// The current operating mode of the pallet. - #[pallet::storage] - #[pallet::getter(fn operating_mode)] - pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; - - #[pallet::call] - impl Pallet { - /// Submit an inbound message originating from the Gateway contract on Ethereum - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::submit())] - pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(!Self::operating_mode().is_halted(), Error::::Halted); - - // submit message to verifier for verification - T::Verifier::verify(&message.event_log, &message.proof) - .map_err(|e| Error::::Verification(e))?; - - // Decode event log into an Envelope - let envelope = - Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; - - // Verify that the message was submitted from the known Gateway contract - ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); - - // Retrieve the registered channel for this message - let channel = - T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::::InvalidChannel)?; - - // Verify message nonce - >::try_mutate(envelope.channel_id, |nonce| -> DispatchResult { - if *nonce == u64::MAX { - return Err(Error::::MaxNonceReached.into()) - } - if envelope.nonce != nonce.saturating_add(1) { - Err(Error::::InvalidNonce.into()) - } else { - *nonce = nonce.saturating_add(1); - Ok(()) - } - })?; - - // Reward relayer from the sovereign account of the destination parachain, only if funds - // are available - let sovereign_account = sibling_sovereign_account::(channel.para_id); - let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32); - let amount = T::Token::reducible_balance( - &sovereign_account, - Preservation::Preserve, - Fortitude::Polite, - ) - .min(delivery_cost); - if !amount.is_zero() { - T::Token::transfer(&sovereign_account, &who, amount, Preservation::Preserve)?; - } - - // Decode payload into `VersionedMessage` - let message = VersionedMessage::decode_all(&mut envelope.payload.as_ref()) - .map_err(|_| Error::::InvalidPayload)?; - - // Decode message into XCM - let (xcm, fee) = Self::do_convert(envelope.message_id, message.clone())?; - - log::info!( - target: LOG_TARGET, - "💫 xcm decoded as {:?} with fee {:?}", - xcm, - fee - ); - - // Burning fees for teleport - Self::burn_fees(channel.para_id, fee)?; - - // Attempt to send XCM to a dest parachain - let message_id = Self::send_xcm(xcm, channel.para_id)?; - - Self::deposit_event(Event::MessageReceived { - channel_id: envelope.channel_id, - nonce: envelope.nonce, - message_id, - fee_burned: fee, - }); - - Ok(()) - } - - /// Halt or resume all pallet operations. May only be called by root. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - mode: BasicOperatingMode, - ) -> DispatchResult { - ensure_root(origin)?; - OperatingMode::::set(mode); - Self::deposit_event(Event::OperatingModeChanged { mode }); - Ok(()) - } - } - - impl Pallet { - pub fn do_convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, BalanceOf), Error> { - let (xcm, fee) = T::MessageConverter::convert(message_id, message) - .map_err(|e| Error::::ConvertMessage(e))?; - Ok((xcm, fee)) - } - - pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result> { - let dest = Location::new(1, [Parachain(dest.into())]); - let (xcm_hash, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; - Ok(xcm_hash) - } - - pub fn calculate_delivery_cost(length: u32) -> BalanceOf { - let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let len_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)); - weight_fee - .saturating_add(len_fee) - .saturating_add(T::PricingParameters::get().rewards.local) - } - - /// Burn the amount of the fee embedded into the XCM for teleports - pub fn burn_fees(para_id: ParaId, fee: BalanceOf) -> DispatchResult { - let dummy_context = - XcmContext { origin: None, message_id: Default::default(), topic: None }; - let dest = Location::new(1, [Parachain(para_id.into())]); - let fees = (Location::parent(), fee.saturated_into::()).into(); - T::AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset check out failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - T::AssetTransactor::check_out(&dest, &fees, &dummy_context); - T::AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { - log::error!( - target: LOG_TARGET, - "XCM asset withdraw failed with error {:?}", error - ); - TokenError::FundsUnavailable - })?; - Ok(()) - } - } - - /// API for accessing the delivery cost of a message - impl Get> for Pallet { - fn get() -> BalanceOf { - // Cost here based on MaxMessagePayloadSize(the worst case) - Self::calculate_delivery_cost(T::MaxMessageSize::get()) - } - } -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs deleted file mode 100644 index 07e0a5564e09..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ /dev/null @@ -1,362 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -use super::*; - -use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee}; -use hex_literal::hex; -use snowbridge_beacon_primitives::{ - types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, -}; -use snowbridge_core::{ - gwei, - inbound::{Log, Proof, VerificationError}, - meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, -}; -use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::{H160, H256}; -use sp_runtime::{ - traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, FixedU128, MultiSignature, -}; -use sp_std::{convert::From, default::Default}; -use xcm::prelude::*; -use xcm_executor::AssetsInHolding; - -use crate::{self as inbound_queue}; - -use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}; - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system::{Pallet, Call, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - EthereumBeaconClient: snowbridge_pallet_ethereum_client::{Pallet, Call, Storage, Event}, - InboundQueue: inbound_queue::{Pallet, Call, Storage, Event}, - } -); - -pub type Signature = MultiSignature; -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - -type Balance = u128; - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type AccountId = AccountId; - type Lookup = IdentityLookup; - type AccountData = pallet_balances::AccountData; - type Block = Block; -} - -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; -} - -parameter_types! { - pub const ChainForkVersions: ForkVersions = ForkVersions{ - genesis: Fork { - version: [0, 0, 0, 1], // 0x00000001 - epoch: 0, - }, - altair: Fork { - version: [1, 0, 0, 1], // 0x01000001 - epoch: 0, - }, - bellatrix: Fork { - version: [2, 0, 0, 1], // 0x02000001 - epoch: 0, - }, - capella: Fork { - version: [3, 0, 0, 1], // 0x03000001 - epoch: 0, - }, - deneb: Fork { - version: [4, 0, 0, 1], // 0x04000001 - epoch: 4294967295, - } - }; -} - -impl snowbridge_pallet_ethereum_client::Config for Test { - type RuntimeEvent = RuntimeEvent; - type ForkVersions = ChainForkVersions; - type FreeHeadersInterval = ConstU32<32>; - type WeightInfo = (); -} - -// Mock verifier -pub struct MockVerifier; - -impl Verifier for MockVerifier { - fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { - Ok(()) - } -} - -const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; - -parameter_types! { - pub const EthereumNetwork: xcm::v3::NetworkId = xcm::v3::NetworkId::Ethereum { chain_id: 11155111 }; - pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const CreateAssetCall: [u8;2] = [53, 0]; - pub const CreateAssetExecutionFee: u128 = 2_000_000_000; - pub const CreateAssetDeposit: u128 = 100_000_000_000; - pub const SendTokenExecutionFee: u128 = 1_000_000_000; - pub const InitialFund: u128 = 1_000_000_000_000; - pub const InboundQueuePalletInstance: u8 = 80; - pub UniversalLocation: InteriorLocation = - [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); - pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); -} - -#[cfg(feature = "runtime-benchmarks")] -impl BenchmarkHelper for Test { - // not implemented since the MockVerifier is used for tests - fn initialize_storage(_: BeaconHeader, _: H256) {} -} - -// Mock XCM sender that always succeeds -pub struct MockXcmSender; - -impl SendXcm for MockXcmSender { - type Ticket = Xcm<()>; - - fn validate( - dest: &mut Option, - xcm: &mut Option>, - ) -> SendResult { - if let Some(location) = dest { - match location.unpack() { - (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), - _ => Ok((xcm.clone().unwrap(), Assets::default())), - } - } else { - Ok((xcm.clone().unwrap(), Assets::default())) - } - } - - fn deliver(xcm: Self::Ticket) -> core::result::Result { - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - Ok(hash) - } -} - -parameter_types! { - pub const OwnParaId: ParaId = ParaId::new(1013); - pub Parameters: PricingParameters = PricingParameters { - exchange_rate: FixedU128::from_rational(1, 400), - fee_per_gas: gwei(20), - rewards: Rewards { local: DOT, remote: meth(1) }, - multiplier: FixedU128::from_rational(1, 1), - }; -} - -pub const DOT: u128 = 10_000_000_000; - -pub struct MockChannelLookup; -impl StaticLookup for MockChannelLookup { - type Source = ChannelId; - type Target = Channel; - - fn lookup(channel_id: Self::Source) -> Option { - if channel_id != - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into() - { - return None - } - Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() }) - } -} - -pub struct SuccessfulTransactor; -impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Ok(()) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Ok(AssetsInHolding::default()) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Ok(AssetsInHolding::default()) - } -} - -pub struct MockTokenIdConvert; -impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::parent()) - } - fn convert_back(_loc: &Location) -> Option { - None - } -} - -impl inbound_queue::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Verifier = MockVerifier; - type Token = Balances; - type XcmSender = MockXcmSender; - type WeightInfo = (); - type GatewayAddress = GatewayAddress; - type MessageConverter = MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - MockTokenIdConvert, - UniversalLocation, - AssetHubFromEthereum, - >; - type PricingParameters = Parameters; - type ChannelLookup = MockChannelLookup; - #[cfg(feature = "runtime-benchmarks")] - type Helper = Test; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type MaxMessageSize = ConstU32<1024>; - type AssetTransactor = SuccessfulTransactor; -} - -pub fn last_events(n: usize) -> Vec { - frame_system::Pallet::::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() -} - -pub fn expect_events(e: Vec) { - assert_eq!(last_events(e.len()), e); -} - -pub fn setup() { - System::set_block_number(1); - Balances::mint_into( - &sibling_sovereign_account::(ASSET_HUB_PARAID.into()), - InitialFund::get(), - ) - .unwrap(); - Balances::mint_into( - &sibling_sovereign_account::(TEMPLATE_PARAID.into()), - InitialFund::get(), - ) - .unwrap(); -} - -pub fn new_tester() -> sp_io::TestExternalities { - let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext: sp_io::TestExternalities = storage.into(); - ext.execute_with(setup); - ext -} - -// Generated from smoketests: -// cd smoketests -// ./make-bindings -// cargo test --test register_token -- --nocapture -pub fn mock_event_log() -> Log { - Log { - // gateway address - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), - } -} - -pub fn mock_event_log_invalid_channel() -> Log { - Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // invalid channel id - hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), - } -} - -pub fn mock_event_log_invalid_gateway() -> Log { - Log { - // gateway address - address: H160::zero(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), - } -} - -pub fn mock_execution_proof() -> ExecutionProof { - ExecutionProof { - header: BeaconHeader::default(), - ancestry_proof: None, - execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { - parent_hash: Default::default(), - fee_recipient: Default::default(), - state_root: Default::default(), - receipts_root: Default::default(), - logs_bloom: vec![], - prev_randao: Default::default(), - block_number: 0, - gas_limit: 0, - gas_used: 0, - timestamp: 0, - extra_data: vec![], - base_fee_per_gas: Default::default(), - block_hash: Default::default(), - transactions_root: Default::default(), - withdrawals_root: Default::default(), - blob_gas_used: 0, - excess_blob_gas: 0, - }), - execution_branch: vec![], - } -} - -pub const ASSET_HUB_PARAID: u32 = 1000u32; -pub const TEMPLATE_PARAID: u32 = 1001u32; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs deleted file mode 100644 index 44f6c0ebc658..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ /dev/null @@ -1,245 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -use super::*; - -use frame_support::{assert_noop, assert_ok}; -use hex_literal::hex; -use snowbridge_core::{inbound::Proof, ChannelId}; -use sp_keyring::AccountKeyring as Keyring; -use sp_runtime::DispatchError; -use sp_std::convert::From; - -use crate::{Error, Event as InboundQueueEvent}; - -use crate::mock::*; - -#[test] -fn test_submit_happy_path() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let channel_sovereign = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - - let origin = RuntimeOrigin::signed(relayer.clone()); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - - let initial_fund = InitialFund::get(); - assert_eq!(Balances::balance(&relayer), 0); - assert_eq!(Balances::balance(&channel_sovereign), initial_fund); - - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") - .into(), - nonce: 1, - message_id: [ - 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, - 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, - ], - fee_burned: 110000000000, - } - .into()]); - - let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); - assert!( - Parameters::get().rewards.local < delivery_cost, - "delivery cost exceeds pure reward" - ); - - assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded"); - assert!( - Balances::balance(&channel_sovereign) <= initial_fund - delivery_cost, - "sovereign account paid reward" - ); - }); -} - -#[test] -fn test_submit_xcm_invalid_channel() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Deposit funds into sovereign account of parachain 1001 - let sovereign_account = sibling_sovereign_account::(TEMPLATE_PARAID.into()); - println!("account: {}", sovereign_account); - let _ = Balances::mint_into(&sovereign_account, 10000); - - // Submit message - let message = Message { - event_log: mock_event_log_invalid_channel(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidChannel, - ); - }); -} - -#[test] -fn test_submit_with_invalid_gateway() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Deposit funds into sovereign account of Asset Hub (Statemint) - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - let _ = Balances::mint_into(&sovereign_account, 10000); - - // Submit message - let message = Message { - event_log: mock_event_log_invalid_gateway(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidGateway - ); - }); -} - -#[test] -fn test_submit_with_invalid_nonce() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Deposit funds into sovereign account of Asset Hub (Statemint) - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - let _ = Balances::mint_into(&sovereign_account, 10000); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - - let nonce: u64 = >::get(ChannelId::from(hex!( - "c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539" - ))); - assert_eq!(nonce, 1); - - // Submit the same again - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidNonce - ); - }); -} - -#[test] -fn test_submit_no_funds_to_reward_relayers_just_ignore() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Reset balance of sovereign_account to zero first - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - Balances::set_balance(&sovereign_account, 0); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - // Check submit successfully in case no funds available - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - }); -} - -#[test] -fn test_set_operating_mode() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - - assert_ok!(InboundQueue::set_operating_mode( - RuntimeOrigin::root(), - snowbridge_core::BasicOperatingMode::Halted - )); - - assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); - }); -} - -#[test] -fn test_set_operating_mode_root_only() { - new_tester().execute_with(|| { - assert_noop!( - InboundQueue::set_operating_mode( - RuntimeOrigin::signed(Keyring::Bob.into()), - snowbridge_core::BasicOperatingMode::Halted - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_submit_no_funds_to_reward_relayers_and_ed_preserved() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Reset balance of sovereign account to (ED+1) first - let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); - Balances::set_balance(&sovereign_account, ExistentialDeposit::get() + 1); - - // Submit message successfully - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - - // Check balance of sovereign account to ED - let amount = Balances::balance(&sovereign_account); - assert_eq!(amount, ExistentialDeposit::get()); - - // Submit another message with nonce set as 2 - let mut event_log = mock_event_log(); - event_log.data[31] = 2; - let message = Message { - event_log, - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - // Check balance of sovereign account as ED does not change - let amount = Balances::balance(&sovereign_account); - assert_eq!(amount, ExistentialDeposit::get()); - }); -} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs deleted file mode 100644 index c2c665f40d9e..000000000000 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Autogenerated weights for `snowbridge_inbound_queue` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 - -#![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 ethereum_beacon_client. -pub trait WeightInfo { - fn submit() -> Weight; -} - -// For backwards compatibility and tests -impl WeightInfo for () { - fn submit() -> Weight { - Weight::from_parts(70_000_000, 0) - .saturating_add(Weight::from_parts(0, 3601)) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index ed94e442c2f7..4127ad68424b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -114,7 +114,6 @@ snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } -snowbridge-pallet-inbound-queue-v2 = { workspace = true } snowbridge-pallet-outbound-queue-v2 = { workspace = true } snowbridge-outbound-queue-runtime-api-v2 = { workspace = true } snowbridge-merkle-tree = { workspace = true } @@ -193,7 +192,6 @@ std = [ "snowbridge-outbound-queue-runtime-api-v2/std", "snowbridge-outbound-queue-runtime-api/std", "snowbridge-pallet-ethereum-client/std", - "snowbridge-pallet-inbound-queue-v2/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue-v2/std", "snowbridge-pallet-outbound-queue/std", @@ -256,7 +254,6 @@ runtime-benchmarks = [ "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", - "snowbridge-pallet-inbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-outbound-queue/runtime-benchmarks", @@ -298,7 +295,6 @@ try-runtime = [ "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", "snowbridge-pallet-ethereum-client/try-runtime", - "snowbridge-pallet-inbound-queue-v2/try-runtime", "snowbridge-pallet-inbound-queue/try-runtime", "snowbridge-pallet-outbound-queue-v2/try-runtime", "snowbridge-pallet-outbound-queue/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index e25caed95a02..633248ca6efb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -26,7 +26,7 @@ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_router_primitives::{ - inbound::{v1::MessageToXcm, v2::MessageToXcm as MessageToXcmV2}, + inbound::v1::MessageToXcm, outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, }; use sp_core::H160; @@ -113,36 +113,6 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type AssetTransactor = ::AssetTransactor; } -impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Verifier = snowbridge_pallet_ethereum_client::Pallet; - type Token = Balances; - #[cfg(not(feature = "runtime-benchmarks"))] - type XcmSender = XcmRouter; - #[cfg(feature = "runtime-benchmarks")] - type XcmSender = DoNothingRouter; - type ChannelLookup = EthereumSystem; - type GatewayAddress = EthereumGatewayAddress; - #[cfg(feature = "runtime-benchmarks")] - type Helper = Runtime; - type MessageConverter = MessageToXcmV2< - CreateAssetCall, - CreateAssetDeposit, - ConstU8, - AccountId, - Balance, - EthereumSystem, - EthereumUniversalLocation, - AssetHubFromEthereum, - >; - type WeightToFee = WeightToFee; - type LengthToFee = ConstantMultiplier; - type MaxMessageSize = ConstU32<2048>; - type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; - type PricingParameters = EthereumSystem; - type AssetTransactor = ::AssetTransactor; -} - impl snowbridge_pallet_outbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Hashing = Keccak256; @@ -257,7 +227,6 @@ pub mod benchmark_helpers { use codec::Encode; use snowbridge_beacon_primitives::BeaconHeader; use snowbridge_pallet_inbound_queue::BenchmarkHelper; - use snowbridge_pallet_inbound_queue_v2::BenchmarkHelper as BenchmarkHelperV2; use sp_core::H256; use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; @@ -267,12 +236,6 @@ pub mod benchmark_helpers { } } - impl BenchmarkHelperV2 for Runtime { - fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) { - EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap(); - } - } - pub struct DoNothingRouter; impl SendXcm for DoNothingRouter { type Ticket = Xcm<()>; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 0ac65c20a86c..28e79ce2f5fe 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -567,7 +567,6 @@ construct_runtime!( EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, EthereumSystem: snowbridge_pallet_system = 83, - EthereumInboundQueueV2: snowbridge_pallet_inbound_queue_v2 = 84, EthereumOutboundQueueV2: snowbridge_pallet_outbound_queue_v2 = 85, // Message Queue. Importantly, is registered last so that messages are processed after @@ -627,7 +626,6 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] - [snowbridge_pallet_inbound_queue_v2, EthereumInboundQueueV2] [snowbridge_pallet_outbound_queue_v2, EthereumOutboundQueueV2] ); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index cba49ab186c5..27746c287933 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -47,7 +47,6 @@ pub mod xcm; pub mod snowbridge_pallet_ethereum_client; pub mod snowbridge_pallet_inbound_queue; -pub mod snowbridge_pallet_inbound_queue_v2; pub mod snowbridge_pallet_outbound_queue; pub mod snowbridge_pallet_outbound_queue_v2; pub mod snowbridge_pallet_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs deleted file mode 100644 index 7844816f903f..000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 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. - -//! Autogenerated weights for `snowbridge_pallet_inbound_queue` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-09-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// target/release/polkadot-parachain -// benchmark -// pallet -// --chain=bridge-hub-rococo-dev -// --pallet=snowbridge_inbound_queue -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --steps -// 50 -// --repeat -// 20 -// --output -// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `snowbridge_pallet_inbound_queue`. -pub struct WeightInfo(PhantomData); -impl snowbridge_pallet_inbound_queue_v2::WeightInfo for WeightInfo { - /// Storage: EthereumInboundQueue PalletOperatingMode (r:1 w:0) - /// Proof: EthereumInboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: EthereumBeaconClient ExecutionHeaders (r:1 w:0) - /// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen) - /// Storage: EthereumInboundQueue Nonce (r:1 w:1) - /// Proof: EthereumInboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - fn submit() -> Weight { - // Proof Size summary in bytes: - // Measured: `800` - // Estimated: `7200` - // Minimum execution time: 200_000_000 picoseconds. - Weight::from_parts(200_000_000, 0) - .saturating_add(Weight::from_parts(0, 7200)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(6)) - } -} From e8951e4798366d330fe2ed3a041af1f5b9d76897 Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 20 Nov 2024 22:09:59 +0800 Subject: [PATCH 20/81] Reorgnize code layout --- .../primitives/router/src/outbound/{v1.rs => v1/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bridges/snowbridge/primitives/router/src/outbound/{v1.rs => v1/mod.rs} (100%) diff --git a/bridges/snowbridge/primitives/router/src/outbound/v1.rs b/bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs similarity index 100% rename from bridges/snowbridge/primitives/router/src/outbound/v1.rs rename to bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs From 2a413991fa35c8b5c337314f81e61671b8274162 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 22 Nov 2024 15:50:48 +0800 Subject: [PATCH 21/81] For Transact --- .../pallets/system/src/benchmarking.rs | 65 +---- bridges/snowbridge/pallets/system/src/lib.rs | 246 +++--------------- bridges/snowbridge/primitives/core/src/lib.rs | 12 + .../primitives/core/src/registry.rs | 13 + .../primitives/core/src/transact.rs | 36 +++ .../router/src/outbound/v2/convert.rs | 34 +++ .../src/tests/snowbridge_v2.rs | 125 ++++++++- .../src/bridge_to_ethereum_config.rs | 4 +- 8 files changed, 254 insertions(+), 281 deletions(-) create mode 100644 bridges/snowbridge/primitives/core/src/registry.rs create mode 100644 bridges/snowbridge/primitives/core/src/transact.rs diff --git a/bridges/snowbridge/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs index 939de9d40d13..e4cb079419d0 100644 --- a/bridges/snowbridge/pallets/system/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system/src/benchmarking.rs @@ -60,74 +60,12 @@ mod benchmarks { Ok(()) } - #[benchmark] - fn create_agent() -> Result<(), BenchmarkError> { - let origin_para_id = 2000; - let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let origin = T::Helper::make_xcm_origin(origin_location); - fund_sovereign_account::(origin_para_id.into())?; - - #[extrinsic_call] - _(origin as T::RuntimeOrigin); - - Ok(()) - } - - #[benchmark] - fn create_channel() -> Result<(), BenchmarkError> { - let origin_para_id = 2000; - let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let origin = T::Helper::make_xcm_origin(origin_location); - fund_sovereign_account::(origin_para_id.into())?; - - SnowbridgeControl::::create_agent(origin.clone())?; - - #[extrinsic_call] - _(origin as T::RuntimeOrigin, OperatingMode::Normal); - - Ok(()) - } - - #[benchmark] - fn update_channel() -> Result<(), BenchmarkError> { - let origin_para_id = 2000; - let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let origin = T::Helper::make_xcm_origin(origin_location); - fund_sovereign_account::(origin_para_id.into())?; - SnowbridgeControl::::create_agent(origin.clone())?; - SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; - - #[extrinsic_call] - _(origin as T::RuntimeOrigin, OperatingMode::RejectingOutboundMessages); - - Ok(()) - } - - #[benchmark] - fn force_update_channel() -> Result<(), BenchmarkError> { - let origin_para_id = 2000; - let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let origin = T::Helper::make_xcm_origin(origin_location); - let channel_id: ChannelId = ParaId::from(origin_para_id).into(); - - fund_sovereign_account::(origin_para_id.into())?; - SnowbridgeControl::::create_agent(origin.clone())?; - SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; - - #[extrinsic_call] - _(RawOrigin::Root, channel_id, OperatingMode::RejectingOutboundMessages); - - Ok(()) - } - #[benchmark] fn transfer_native_from_agent() -> Result<(), BenchmarkError> { let origin_para_id = 2000; let origin_location = Location::new(1, [Parachain(origin_para_id)]); let origin = T::Helper::make_xcm_origin(origin_location); fund_sovereign_account::(origin_para_id.into())?; - SnowbridgeControl::::create_agent(origin.clone())?; - SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, H160::default(), 1); @@ -139,9 +77,8 @@ mod benchmarks { fn force_transfer_native_from_agent() -> Result<(), BenchmarkError> { let origin_para_id = 2000; let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let origin = T::Helper::make_xcm_origin(origin_location.clone()); + let _origin = T::Helper::make_xcm_origin(origin_location.clone()); fund_sovereign_account::(origin_para_id.into())?; - SnowbridgeControl::::create_agent(origin.clone())?; let versioned_location: VersionedLocation = origin_location.into(); diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 8a5b0a6edbf9..da927061da65 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -15,26 +15,16 @@ //! The `create_agent` extrinsic should be called via an XCM `Transact` instruction from the sibling //! parachain. //! -//! ## Channels -//! -//! Each sibling parachain has its own dedicated messaging channel for sending and receiving -//! messages. As a prerequisite to creating a channel, the sibling should have already created -//! an agent using the `create_agent` extrinsic. -//! -//! * [`Call::create_channel`]: Create channel for a sibling -//! * [`Call::update_channel`]: Update a channel for a sibling -//! //! ## Governance //! //! Only Polkadot governance itself can call these extrinsics. Delivery fees are waived. //! //! * [`Call::upgrade`]`: Upgrade the gateway contract //! * [`Call::set_operating_mode`]: Update the operating mode of the gateway contract -//! * [`Call::force_update_channel`]: Allow root to update a channel for a sibling //! * [`Call::force_transfer_native_from_agent`]: Allow root to withdraw ether from an agent //! //! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and -//! `force_update_channel` and extrinsics to manage agents and channels for system parachains. +//! `force_update_channel` and extrinsics to manage agents for system parachains. //! //! ## Polkadot-native tokens on Ethereum //! @@ -73,6 +63,7 @@ use snowbridge_core::{ v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, OperatingMode, SendError, }, + registry::{AgentRegistry, TokenRegistry}, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, @@ -102,8 +93,8 @@ fn ensure_sibling(location: &Location) -> Result<(ParaId, H256), DispatchErro where T: Config, { - match location.unpack() { - (1, [Parachain(para_id)]) => { + match (location.parents, location.first_interior()) { + (1, Some(Parachain(para_id))) => { let agent_id = agent_id_of::(location)?; Ok(((*para_id).into(), agent_id)) }, @@ -141,7 +132,7 @@ where #[frame_support::pallet] pub mod pallet { use frame_support::dispatch::PostDispatchInfo; - use snowbridge_core::{outbound::v2::second_governance_origin, StaticLookup}; + use snowbridge_core::StaticLookup; use sp_core::U256; use super::*; @@ -255,6 +246,7 @@ pub mod pallet { InvalidTokenTransferFees, InvalidPricingParameters, InvalidUpgradeParameters, + TokenAlreadyCreated, } /// The set of registered agents @@ -382,126 +374,6 @@ pub mod pallet { Ok(()) } - /// Sends a command to the Gateway contract to instantiate a new agent contract representing - /// `origin`. - /// - /// Fee required: Yes - /// - /// - `origin`: Must be `Location` of a sibling parachain - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::create_agent())] - pub fn create_agent(origin: OriginFor) -> DispatchResult { - let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; - - // Ensure that origin location is some consensus system on a sibling parachain - let (para_id, agent_id) = ensure_sibling::(&origin_location)?; - - // Record the agent id or fail if it has already been created - ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); - Agents::::insert(agent_id, ()); - - let command = Command::CreateAgent { agent_id }; - let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); - Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; - - Self::deposit_event(Event::::CreateAgent { - location: Box::new(origin_location), - agent_id, - }); - Ok(()) - } - - /// Sends a message to the Gateway contract to create a new channel representing `origin` - /// - /// Fee required: Yes - /// - /// This extrinsic is permissionless, so a fee is charged to prevent spamming and pay - /// for execution costs on the remote side. - /// - /// The message is sent over the bridge on BridgeHub's own channel to the Gateway. - /// - /// - `origin`: Must be `Location` - /// - `mode`: Initial operating mode of the channel - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::create_channel())] - pub fn create_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { - let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; - - // Ensure that origin location is a sibling parachain - let (para_id, agent_id) = ensure_sibling::(&origin_location)?; - - let channel_id: ChannelId = para_id.into(); - - ensure!(Agents::::contains_key(agent_id), Error::::NoAgent); - ensure!(!Channels::::contains_key(channel_id), Error::::ChannelAlreadyCreated); - - let channel = Channel { agent_id, para_id }; - Channels::::insert(channel_id, channel); - - let command = Command::CreateChannel { channel_id, agent_id, mode }; - let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); - Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; - - Self::deposit_event(Event::::CreateChannel { channel_id, agent_id }); - Ok(()) - } - - /// Sends a message to the Gateway contract to update a channel configuration - /// - /// The origin must already have a channel initialized, as this message is sent over it. - /// - /// A partial fee will be charged for local processing only. - /// - /// - `origin`: Must be `Location` - /// - `mode`: Initial operating mode of the channel - #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::update_channel())] - pub fn update_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { - let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; - - // Ensure that origin location is a sibling parachain - let (para_id, _) = ensure_sibling::(&origin_location)?; - - let channel_id: ChannelId = para_id.into(); - - ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); - - let command = Command::UpdateChannel { channel_id, mode }; - let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); - - // Parachains send the update message on their own channel - Self::send(channel_id, command, pays_fee)?; - - Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); - Ok(()) - } - - /// Sends a message to the Gateway contract to update an arbitrary channel - /// - /// Fee required: No - /// - /// - `origin`: Must be root - /// - `channel_id`: ID of channel - /// - `mode`: Initial operating mode of the channel - /// - `outbound_fee`: Fee charged to users for sending outbound messages to Polkadot - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::force_update_channel())] - pub fn force_update_channel( - origin: OriginFor, - channel_id: ChannelId, - mode: OperatingMode, - ) -> DispatchResult { - ensure_root(origin)?; - - ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); - - let command = Command::UpdateChannel { channel_id, mode }; - Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; - - Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); - Ok(()) - } - /// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`. /// /// A partial fee will be charged for local processing only. @@ -641,34 +513,6 @@ pub mod pallet { pays_fee: Pays::No, }) } - - /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. - /// Privileged. Can only be called by root. - /// - /// Fee required: No - /// - /// - `origin`: Must be root - /// - `location`: Location of the asset (relative to this chain) - /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum - #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::register_token())] - pub fn register_token_v2( - origin: OriginFor, - location: Box, - metadata: AssetMetadata, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - let location: Location = - (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; - - Self::do_register_token_v2(&location, metadata, PaysFee::::No)?; - - Ok(PostDispatchInfo { - actual_weight: Some(T::WeightInfo::register_token()), - pays_fee: Pays::No, - }) - } } impl Pallet { @@ -795,44 +639,9 @@ pub mod pallet { Ok(()) } - pub(crate) fn do_register_token_v2( - location: &Location, - metadata: AssetMetadata, - pays_fee: PaysFee, - ) -> Result<(), DispatchError> { - let ethereum_location = T::EthereumLocation::get(); - // reanchor to Ethereum context - let location = location - .clone() - .reanchored(ðereum_location, &T::UniversalLocation::get()) - .map_err(|_| Error::::LocationConversionFailed)?; - - let token_id = TokenIdOf::convert_location(&location) - .ok_or(Error::::LocationConversionFailed)?; - - if !ForeignToNativeId::::contains_key(token_id) { - NativeToForeignId::::insert(location.clone(), token_id); - ForeignToNativeId::::insert(token_id, location.clone()); - } - - let command = CommandV2::RegisterForeignToken { - token_id, - name: metadata.name.into_inner(), - symbol: metadata.symbol.into_inner(), - decimals: metadata.decimals, - }; - Self::send_v2(second_governance_origin(), command, pays_fee)?; - - Self::deposit_event(Event::::RegisterToken { - location: location.clone().into(), - foreign_token_id: token_id, - }); - - Ok(()) - } - + #[allow(dead_code)] /// Send `command` to the Gateway on the Channel identified by `channel_id` - fn send_v2(origin: H256, command: CommandV2, pays_fee: PaysFee) -> DispatchResult { + fn send_governance_command(origin: H256, command: CommandV2) -> DispatchResult { let message = MessageV2 { origin, id: Default::default(), @@ -840,23 +649,9 @@ pub mod pallet { commands: BoundedVec::try_from(vec![command]).unwrap(), }; - let (ticket, fee) = + let (ticket, _) = T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; - let payment = match pays_fee { - PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee)), - PaysFee::No => None, - }; - - if let Some((payer, fee)) = payment { - T::Token::transfer( - &payer, - &T::TreasuryAccount::get(), - fee, - Preservation::Preserve, - )?; - } - T::OutboundQueueV2::deliver(ticket).map_err(|err| Error::::Send(err))?; Ok(()) } @@ -890,4 +685,27 @@ pub mod pallet { NativeToForeignId::::get(location) } } + + impl TokenRegistry for Pallet { + fn register(location: Location) -> DispatchResult { + ensure!( + NativeToForeignId::::contains_key(location.clone()), + Error::::TokenAlreadyCreated + ); + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + ForeignToNativeId::::insert(token_id, location.clone()); + NativeToForeignId::::insert(location.clone(), token_id); + Ok(()) + } + } + + impl AgentRegistry for Pallet { + fn register(location: Location) -> DispatchResult { + let agent_id = agent_id_of::(&location)?; + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + Ok(()) + } + } } diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 88ac8124a15b..7a9a8df544d7 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -13,8 +13,10 @@ pub mod location; pub mod operating_mode; pub mod outbound; pub mod pricing; +pub mod registry; pub mod reward; pub mod ringbuffer; +pub mod transact; pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; pub use polkadot_parachain_primitives::primitives::{ @@ -54,6 +56,16 @@ impl Contains for AllowSiblingsOnly { } } +pub struct AllowAnySovereignFromSiblings; +impl Contains for AllowAnySovereignFromSiblings { + fn contains(location: &Location) -> bool { + match (location.parent_count(), location.first_interior()) { + (1, Some(Parachain(..))) => true, + _ => false, + } + } +} + pub fn gwei(x: u128) -> U256 { U256::from(1_000_000_000u128).saturating_mul(x.into()) } diff --git a/bridges/snowbridge/primitives/core/src/registry.rs b/bridges/snowbridge/primitives/core/src/registry.rs new file mode 100644 index 000000000000..f3b87bdbbfac --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/registry.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +use frame_support::dispatch::DispatchResult; +use xcm::prelude::Location; + +pub trait TokenRegistry { + fn register(location: Location) -> DispatchResult; +} + +pub trait AgentRegistry { + fn register(location: Location) -> DispatchResult; +} diff --git a/bridges/snowbridge/primitives/core/src/transact.rs b/bridges/snowbridge/primitives/core/src/transact.rs new file mode 100644 index 000000000000..ab18a3d6202d --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/transact.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +use crate::{AssetMetadata, Vec}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::H160; +use sp_runtime::RuntimeDebug; +use xcm::prelude::Location; + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct TransactInfo { + pub kind: TransactKind, + pub params: Vec, +} + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub enum TransactKind { + RegisterToken, + RegisterAgent, + CallContract, +} + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct RegisterTokenParams { + pub location: Location, + pub metadata: AssetMetadata, +} + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CallContractParams { + pub target: H160, + pub call: Vec, + pub gas_limit: u64, + pub value: u128, +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 65f8063686a0..3319ff6877f8 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -2,10 +2,12 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts XCM messages into InboundMessage that can be processed by the Gateway contract +use codec::DecodeAll; use core::slice::Iter; use frame_support::{ensure, BoundedVec}; use snowbridge_core::{ outbound::v2::{Command, Message}, + transact::{CallContractParams, RegisterTokenParams, TransactInfo, TransactKind::*}, AgentId, TokenId, TokenIdOf, TokenIdOf as LocationIdOf, }; use sp_core::H160; @@ -35,6 +37,7 @@ pub enum XcmConverterError { TooManyCommands, AliasOriginExpected, InvalidOrigin, + TransactDecodeFailed, } macro_rules! match_expression { @@ -160,6 +163,7 @@ where let mut commands: Vec = Vec::new(); + // ENA transfer commands if let Some(enas) = enas { ensure!(enas.len() > 0, NoReserveAssets); for ena in enas.clone().inner().iter() { @@ -193,6 +197,7 @@ where } } + // PNA transfer commands if let Some(pnas) = pnas { ensure!(pnas.len() > 0, NoReserveAssets); for pna in pnas.clone().inner().iter() { @@ -221,6 +226,35 @@ where } } + // Transact commands + let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call); + if let Some(transact_call) = transact_call { + let _ = self.next(); + let message = + TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) + .map_err(|_| TransactDecodeFailed)?; + match message.kind { + RegisterAgent => commands.push(Command::CreateAgent { agent_id: origin }), + RegisterToken => { + let params = RegisterTokenParams::decode_all(&mut message.params.as_slice()) + .map_err(|_| TransactDecodeFailed)?; + let token_id = + TokenIdOf::convert_location(¶ms.location).ok_or(InvalidAsset)?; + commands.push(Command::RegisterForeignToken { + token_id, + name: params.metadata.name.into_inner(), + symbol: params.metadata.symbol.into_inner(), + decimals: params.metadata.decimals, + }); + }, + // Todo: For Transact + CallContract => { + let _ = CallContractParams::decode_all(&mut message.params.as_slice()) + .map_err(|_| TransactDecodeFailed)?; + }, + } + } + // ensure SetTopic exists let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 65b887ebcdc0..2c297c171173 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -14,8 +14,12 @@ // limitations under the License. use crate::imports::*; use bridge_hub_westend_runtime::EthereumInboundQueue; +use frame_support::traits::fungibles::Mutate; use hex_literal::hex; -use snowbridge_core::AssetMetadata; +use snowbridge_core::{ + transact::{TransactInfo, TransactKind}, + AssetMetadata, +}; use snowbridge_router_primitives::inbound::{ v1::{Command, Destination, MessageV1, VersionedMessage}, EthereumLocationsConverterFor, @@ -32,6 +36,40 @@ const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EB const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; +pub fn fund_sovereign() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn register_weth() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + + assert_ok!(::ForeignAssets::mint_into( + weth_asset_location.clone().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + TOKEN_AMOUNT, + )); + }); +} + #[test] fn send_weth_from_asset_hub_to_ethereum() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); @@ -468,3 +506,88 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { ); }); } + +#[test] +fn create_agent() { + let weth_asset_location: Location = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + + fund_sovereign(); + + register_weth(); + + BridgeHubWestend::execute_with(|| {}); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_amount = 200_000_000_000; + + let remote_fee_amount = 4_000_000_000; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + let remote_fee_asset = + Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + let reserve_asset = Asset { + id: AssetId(weth_asset_location.clone()), + fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), + }; + let assets = vec![ + Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }, + local_fee_asset.clone(), + ]; + let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + + let beneficiary = Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + ); + + let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset.clone() }, + InitiateTransfer { + destination, + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset.clone().into(), + ))), + preserve_origin: true, + assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( + reserve_asset.clone().into(), + ))], + remote_xcm: Xcm(vec![ + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + Transact { + origin_kind: OriginKind::SovereignAccount, + call: transact_info.encode().into(), + }, + ]), + }, + ])); + + // Send the Weth back to Ethereum + ::PolkadotXcm::execute( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + bx!(xcms), + Weight::from(8_000_000_000), + ) + .unwrap(); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + }); +} + +#[test] +fn transact_with_agent() {} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 633248ca6efb..ffb75a510cb5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -24,7 +24,7 @@ use crate::{ }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; -use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; +use snowbridge_core::{gwei, meth, AllowAnySovereignFromSiblings, PricingParameters, Rewards}; use snowbridge_router_primitives::{ inbound::v1::MessageToXcm, outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, @@ -207,7 +207,7 @@ impl snowbridge_pallet_ethereum_client::Config for Runtime { impl snowbridge_pallet_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OutboundQueue = EthereumOutboundQueue; - type SiblingOrigin = EnsureXcm; + type SiblingOrigin = EnsureXcm; type AgentIdOf = snowbridge_core::AgentIdOf; type TreasuryAccount = TreasuryAccount; type Token = Balances; From 66afb0760b0f8877e2ca2481b659dc13cef6f372 Mon Sep 17 00:00:00 2001 From: ron Date: Sat, 23 Nov 2024 09:22:35 +0800 Subject: [PATCH 22/81] Transact support --- .../primitives/core/src/outbound/v2.rs | 49 +- .../primitives/core/src/transact.rs | 2 +- .../router/src/outbound/v2/convert.rs | 16 +- .../src/tests/snowbridge_v2.rs | 503 +++++++----------- 4 files changed, 240 insertions(+), 330 deletions(-) diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs index 59be8767c2f4..e9ff47786363 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -12,10 +12,10 @@ use sp_core::{RuntimeDebug, H160, H256}; use sp_std::{vec, vec::Vec}; use crate::outbound::v2::abi::{ - CreateAgentParams, MintForeignTokenParams, RegisterForeignTokenParams, SetOperatingModeParams, + CallContractParams, MintForeignTokenParams, RegisterForeignTokenParams, SetOperatingModeParams, UnlockNativeTokenParams, UpgradeParams, }; -use alloy_primitives::{Address, FixedBytes}; +use alloy_primitives::{Address, FixedBytes, U256}; use alloy_sol_types::SolValue; pub mod abi { @@ -54,12 +54,6 @@ pub mod abi { bytes initParams; } - // Payload for CreateAgent - struct CreateAgentParams { - /// @dev The agent ID of the consensus system - bytes32 agentID; - } - // Payload for SetOperatingMode instruction struct SetOperatingModeParams { /// The new operating mode @@ -97,6 +91,16 @@ pub mod abi { // Amount to mint uint128 amount; } + + // Payload for CallContract + struct CallContractParams { + // target contract + address target; + // Call data + bytes data; + // Ether value + uint256 value; + } } #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] @@ -138,11 +142,7 @@ pub enum Command { initializer: Option, }, /// Create an agent representing a consensus system on Polkadot - CreateAgent { - /// The ID of the agent, derived from the `MultiLocation` of the consensus system on - /// Polkadot - agent_id: H256, - }, + CreateAgent {}, /// Set the global operating mode of the Gateway contract SetOperatingMode { /// The new operating mode @@ -179,6 +179,17 @@ pub enum Command { /// The amount of tokens to mint amount: u128, }, + /// Call Contract on Ethereum + CallContract { + /// Target contract address + target: H160, + /// The call data to the contract + data: Vec, + /// The dynamic gas component that needs to be specified when executing the contract + gas_limit: u64, + /// Ether Value(require to prefund the agent first) + value: u128, + }, } impl Command { @@ -191,6 +202,7 @@ impl Command { Command::RegisterForeignToken { .. } => 3, Command::MintForeignToken { .. } => 4, Command::CreateAgent { .. } => 5, + Command::CallContract { .. } => 6, } } @@ -203,9 +215,7 @@ impl Command { initParams: initializer.clone().map_or(vec![], |i| i.params), } .abi_encode(), - Command::CreateAgent { agent_id } => - CreateAgentParams { agentID: FixedBytes::from(agent_id.as_fixed_bytes()) } - .abi_encode(), + Command::CreateAgent {} => vec![], Command::SetOperatingMode { mode } => SetOperatingModeParams { mode: (*mode) as u8 }.abi_encode(), Command::UnlockNativeToken { token, recipient, amount, .. } => @@ -229,6 +239,12 @@ impl Command { amount: *amount, } .abi_encode(), + Command::CallContract { target, data, value, .. } => CallContractParams { + target: Address::from(target.as_fixed_bytes()), + data: data.to_vec(), + value: U256::try_from(*value).unwrap(), + } + .abi_encode(), } } } @@ -290,6 +306,7 @@ impl GasMeter for ConstantGasMeter { Command::UnlockNativeToken { .. } => 100_000, Command::RegisterForeignToken { .. } => 1_200_000, Command::MintForeignToken { .. } => 100_000, + Command::CallContract { gas_limit, .. } => *gas_limit, } } } diff --git a/bridges/snowbridge/primitives/core/src/transact.rs b/bridges/snowbridge/primitives/core/src/transact.rs index ab18a3d6202d..0dc77555ae45 100644 --- a/bridges/snowbridge/primitives/core/src/transact.rs +++ b/bridges/snowbridge/primitives/core/src/transact.rs @@ -30,7 +30,7 @@ pub struct RegisterTokenParams { #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] pub struct CallContractParams { pub target: H160, - pub call: Vec, + pub data: Vec, pub gas_limit: u64, pub value: u128, } diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 3319ff6877f8..69ee69991e9f 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -165,7 +165,6 @@ where // ENA transfer commands if let Some(enas) = enas { - ensure!(enas.len() > 0, NoReserveAssets); for ena in enas.clone().inner().iter() { // Check the the deposit asset filter matches what was reserved. if !deposit_assets.matches(ena) { @@ -234,7 +233,7 @@ where TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) .map_err(|_| TransactDecodeFailed)?; match message.kind { - RegisterAgent => commands.push(Command::CreateAgent { agent_id: origin }), + RegisterAgent => commands.push(Command::CreateAgent {}), RegisterToken => { let params = RegisterTokenParams::decode_all(&mut message.params.as_slice()) .map_err(|_| TransactDecodeFailed)?; @@ -247,10 +246,19 @@ where decimals: params.metadata.decimals, }); }, - // Todo: For Transact CallContract => { - let _ = CallContractParams::decode_all(&mut message.params.as_slice()) + let params = CallContractParams::decode_all(&mut message.params.as_slice()) .map_err(|_| TransactDecodeFailed)?; + if params.value > 0 { + //Todo: Ensure amount of WETH deposit to the agent in same message can + // cover the value here + } + commands.push(Command::CallContract { + target: params.target, + data: params.data, + gas_limit: params.gas_limit, + value: params.value, + }); }, } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 2c297c171173..a610ddc70bf9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -13,28 +13,46 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use bridge_hub_westend_runtime::EthereumInboundQueue; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use snowbridge_core::{ - transact::{TransactInfo, TransactKind}, + transact::{CallContractParams, TransactInfo, TransactKind}, AssetMetadata, }; -use snowbridge_router_primitives::inbound::{ - v1::{Command, Destination, MessageV1, VersionedMessage}, - EthereumLocationsConverterFor, -}; +use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use sp_runtime::MultiAddress; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 5_000_000_000_000; +const INITIAL_FUND: u128 = 50_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); -const XCM_FEE: u128 = 100_000_000_000; +const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); const TOKEN_AMOUNT: u128 = 100_000_000_000; +const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 4_000_000_000; +const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 200_000_000_000; + +const EXECUTION_WEIGHT: u64 = 8_000_000_000; + +pub fn weth_location() -> Location { + Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { network: None, key: WETH }, + ], + ) +} + +pub fn destination() -> Location { + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]) +} + +pub fn beneficiary() -> Location { + Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) +} pub fn fund_sovereign() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); @@ -45,121 +63,91 @@ pub fn fund_sovereign() { pub fn register_weth() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - let weth_asset_location: Location = - (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; assert_ok!(::ForeignAssets::force_create( RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), + weth_location().try_into().unwrap(), assethub_sovereign.clone().into(), false, 1, )); assert!(::ForeignAssets::asset_exists( - weth_asset_location.clone().try_into().unwrap(), + weth_location().try_into().unwrap(), )); assert_ok!(::ForeignAssets::mint_into( - weth_asset_location.clone().try_into().unwrap(), + weth_location().try_into().unwrap(), &AssetHubWestendReceiver::get(), TOKEN_AMOUNT, )); - }); -} - -#[test] -fn send_weth_from_asset_hub_to_ethereum() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - let weth_asset_location: Location = - (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); - - BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), - assethub_sovereign.clone().into(), - false, - 1, - )); - assert!(::ForeignAssets::asset_exists( - weth_asset_location.clone().try_into().unwrap(), + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + TOKEN_AMOUNT, )); }); - +} +pub fn register_relay_token() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; - let message = VersionedMessage::V1(MessageV1 { - chain_id: CHAIN_ID, - command: Command::SendToken { - token: WETH.into(), - destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, - amount: TOKEN_AMOUNT, - fee: XCM_FEE, + // Register WND on BH + assert_ok!(::Balances::force_set_balance( + RuntimeOrigin::root(), + MultiAddress::Id(BridgeHubWestendSender::get()), + INITIAL_FUND, + )); + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(Location::parent())), + AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, }, - }); - let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); - let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); - - // Check that the send token message was sent using xcm + )); assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] ); }); +} + +#[test] +fn send_weth_from_asset_hub_to_ethereum() { + fund_sovereign(); + + register_weth(); AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; type RuntimeOrigin = ::RuntimeOrigin; - // Check that AssetHub has issued the foreign asset - assert_expected_events!( - AssetHubWestend, - vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] - ); - - // Local fee amount(in DOT) should cover - // 1. execution cost on AH - // 2. delivery cost to BH - // 3. execution cost on BH - let local_fee_amount = 200_000_000_000; - // Remote fee amount(in WETH) should cover execution cost on Ethereum - let remote_fee_amount = 4_000_000_000; let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; + let remote_fee_asset = - Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; + let reserve_asset = Asset { - id: AssetId(weth_asset_location.clone()), - fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), + id: AssetId(weth_location()), + fun: Fungible(TOKEN_AMOUNT - REMOTE_FEE_AMOUNT_IN_WETH), }; + let assets = vec![ - Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }, + Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }, local_fee_asset.clone(), ]; - let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); - - let beneficiary = Location::new( - 0, - [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], - ); - - let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]); - let xcms = VersionedXcm::from(Xcm(vec![ + let xcm = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination, + destination: destination(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -167,147 +155,68 @@ fn send_weth_from_asset_hub_to_ethereum() { assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( reserve_asset.clone().into(), ))], - remote_xcm: xcm_on_bh, + remote_xcm: Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: beneficiary(), + }]), }, ])); // Send the Weth back to Ethereum ::PolkadotXcm::execute( RuntimeOrigin::signed(AssetHubWestendReceiver::get()), - bx!(xcms), - Weight::from(8_000_000_000), + bx!(xcm), + Weight::from(EXECUTION_WEIGHT), ) .unwrap(); }); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the transfer token back to Ethereum message was queue in the Ethereum - // Outbound Queue + // Check that the Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] ); - let events = BridgeHubWestend::events(); - // Check that the remote fee was credited to the AssetHub sovereign account - assert!( - events.iter().any(|event| matches!( - event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) - if *who == assethub_sovereign - )), - "AssetHub sovereign takes remote fee." - ); }); } #[test] fn transfer_relay_token() { - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( - BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()), - ); - BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); - - let asset_id: Location = Location { parents: 1, interior: [].into() }; - let _expected_asset_id: Location = Location { - parents: 1, - interior: [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))].into(), - }; - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - - // Register token - BridgeHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - type RuntimeEvent = ::RuntimeEvent; + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&destination()) + .unwrap() + .into(); - assert_ok!(::Balances::force_set_balance( - RuntimeOrigin::root(), - sp_runtime::MultiAddress::Id(BridgeHubWestendSender::get()), - INITIAL_FUND * 10, - )); + fund_sovereign(); - assert_ok!(::EthereumSystem::register_token( - RuntimeOrigin::root(), - Box::new(VersionedLocation::from(asset_id.clone())), - AssetMetadata { - name: "wnd".as_bytes().to_vec().try_into().unwrap(), - symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), - decimals: 12, - }, - )); - // Check that a message was sent to Ethereum to create the agent - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] - ); - }); + register_weth(); + + register_relay_token(); // Send token to Ethereum AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; type RuntimeEvent = ::RuntimeEvent; - let weth_asset_location: Location = - (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }) - .into(); - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), - assethub_sovereign.clone().into(), - false, - 1, - )); - - assert_ok!(::ForeignAssets::mint( - RuntimeOrigin::signed(assethub_sovereign.clone().into()), - weth_asset_location.clone().try_into().unwrap(), - MultiAddress::Id(AssetHubWestendSender::get()), - TOKEN_AMOUNT, - )); - - // Local fee amount(in DOT) should cover - // 1. execution cost on AH - // 2. delivery cost to BH - // 3. execution cost on BH - let local_fee_amount = 200_000_000_000; - // Remote fee amount(in WETH) should cover execution cost on Ethereum - let remote_fee_amount = 4_000_000_000; - let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; let remote_fee_asset = - Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; let assets = vec![ Asset { id: AssetId(Location::parent()), - fun: Fungible(TOKEN_AMOUNT + local_fee_amount), + fun: Fungible(TOKEN_AMOUNT + LOCAL_FEE_AMOUNT_IN_DOT), }, remote_fee_asset.clone(), ]; - let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); - - let beneficiary = Location::new( - 0, - [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], - ); - - let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]); - - let xcms = VersionedXcm::from(Xcm(vec![ + let xcm = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination, + destination: destination(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -315,15 +224,18 @@ fn transfer_relay_token() { assets: vec![AssetTransferFilter::ReserveDeposit(Definite( Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }.into(), ))], - remote_xcm: xcm_on_bh, + remote_xcm: Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: beneficiary(), + }]), }, ])); // Send DOT to Ethereum ::PolkadotXcm::execute( RuntimeOrigin::signed(AssetHubWestendSender::get()), - bx!(xcms), - Weight::from(8_000_000_000), + bx!(xcm), + Weight::from(EXECUTION_WEIGHT), ) .unwrap(); @@ -342,8 +254,7 @@ fn transfer_relay_token() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the transfer token back to Ethereum message was queue in the Ethereum - // Outbound Queue + // Check that the Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] @@ -353,127 +264,113 @@ fn transfer_relay_token() { #[test] fn send_weth_and_dot_from_asset_hub_to_ethereum() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - let weth_asset_location: Location = - (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + fund_sovereign(); - BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + register_weth(); + + register_relay_token(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; - // Register WETH on AH - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), - assethub_sovereign.clone().into(), - false, - 1, - )); + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; + let remote_fee_asset = + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; - assert!(::ForeignAssets::asset_exists( - weth_asset_location.clone().try_into().unwrap(), - )); - }); + let reserve_asset = Asset { + id: AssetId(weth_location()), + fun: Fungible(TOKEN_AMOUNT - REMOTE_FEE_AMOUNT_IN_WETH), + }; - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; + let weth_asset = Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }; - // Register WND on BH - assert_ok!(::Balances::force_set_balance( - RuntimeOrigin::root(), - MultiAddress::Id(BridgeHubWestendSender::get()), - INITIAL_FUND * 10, - )); - assert_ok!(::EthereumSystem::register_token( - RuntimeOrigin::root(), - Box::new(VersionedLocation::from(Location::parent())), - AssetMetadata { - name: "wnd".as_bytes().to_vec().try_into().unwrap(), - symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), - decimals: 12, - }, - )); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] - ); + let dot_asset = Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }; + + let assets = vec![weth_asset, dot_asset.clone(), local_fee_asset.clone()]; - // Transfer some WETH to AH - let message = VersionedMessage::V1(MessageV1 { - chain_id: CHAIN_ID, - command: Command::SendToken { - token: WETH.into(), - destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() }, - amount: TOKEN_AMOUNT, - fee: XCM_FEE, + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset.clone() }, + InitiateTransfer { + destination: destination(), + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset.clone().into(), + ))), + preserve_origin: true, + assets: vec![ + AssetTransferFilter::ReserveWithdraw(Definite(reserve_asset.clone().into())), + AssetTransferFilter::ReserveDeposit(Definite(dot_asset.into())), + ], + remote_xcm: Xcm(vec![DepositAsset { + assets: Wild(All), + beneficiary: beneficiary(), + }]), }, - }); - let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); - let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + ])); + + ::PolkadotXcm::execute( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + bx!(xcms), + Weight::from(EXECUTION_WEIGHT), + ) + .unwrap(); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] ); }); +} - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; +#[test] +fn create_agent() { + fund_sovereign(); - // Check that AssetHub has issued the foreign asset - assert_expected_events!( - AssetHubWestend, - vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] - ); + register_weth(); - // Local fee amount(in DOT) should cover - // 1. execution cost on AH - // 2. delivery cost to BH - // 3. execution cost on BH - let local_fee_amount = 200_000_000_000; - // Remote fee amount(in WETH) should cover execution cost on Ethereum - let remote_fee_amount = 4_000_000_000; + BridgeHubWestend::execute_with(|| {}); - let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; - let remote_fee_asset = - Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; - let reserve_asset = Asset { - id: AssetId(weth_asset_location.clone()), - fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), - }; + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; - let weth_asset = - Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }; - let dot_asset = Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }; + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; - let assets = vec![weth_asset, dot_asset.clone(), local_fee_asset.clone()]; - let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + // All WETH as fee and reserve_asset is zero, so there is no transfer in this case + let remote_fee_asset = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) }; + let reserve_asset = Asset { id: AssetId(weth_location()), fun: Fungible(0) }; - let beneficiary = Location::new( - 0, - [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], - ); + let assets = vec![ + Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }, + local_fee_asset.clone(), + ]; - let xcm_on_bh = Xcm(vec![DepositAsset { assets: Wild(All), beneficiary }]); + let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; let xcms = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination, + destination: destination(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), preserve_origin: true, - assets: vec![ - AssetTransferFilter::ReserveWithdraw(Definite(reserve_asset.clone().into())), - AssetTransferFilter::ReserveDeposit(Definite(dot_asset.into())), - ], - remote_xcm: xcm_on_bh, + assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( + reserve_asset.clone().into(), + ))], + remote_xcm: Xcm(vec![ + DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary() }, + Transact { + origin_kind: OriginKind::SovereignAccount, + call: transact_info.encode().into(), + }, + ]), }, ])); @@ -488,27 +385,16 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the transfer token back to Ethereum message was queue in the Ethereum - // Outbound Queue + // Check that Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] ); - let events = BridgeHubWestend::events(); - // Check that the remote fee was credited to the AssetHub sovereign account - assert!( - events.iter().any(|event| matches!( - event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) - if *who == assethub_sovereign - )), - "AssetHub sovereign takes remote fee." - ); }); } #[test] -fn create_agent() { +fn transact_with_agent() { let weth_asset_location: Location = (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); @@ -519,39 +405,43 @@ fn create_agent() { BridgeHubWestend::execute_with(|| {}); AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; type RuntimeOrigin = ::RuntimeOrigin; - let local_fee_amount = 200_000_000_000; - - let remote_fee_amount = 4_000_000_000; - let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(local_fee_amount) }; - let remote_fee_asset = - Asset { id: AssetId(weth_asset_location.clone()), fun: Fungible(remote_fee_amount) }; + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; + + let remote_fee_asset = Asset { + id: AssetId(weth_asset_location.clone()), + fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH), + }; let reserve_asset = Asset { id: AssetId(weth_asset_location.clone()), - fun: Fungible(TOKEN_AMOUNT - remote_fee_amount), + fun: Fungible(TOKEN_AMOUNT - REMOTE_FEE_AMOUNT_IN_WETH), }; + let assets = vec![ Asset { id: weth_asset_location.clone().into(), fun: Fungible(TOKEN_AMOUNT) }, local_fee_asset.clone(), ]; - let destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); - let beneficiary = Location::new( - 0, - [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], - ); + let beneficiary = + Location::new(0, [AccountKey20 { network: None, key: AGENT_ADDRESS.into() }]); - let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; + let call_params = CallContractParams { + target: Default::default(), + data: vec![], + gas_limit: 40000, + // value should be less than the transfer amount, require validation on BH Exporter + value: 4 * (TOKEN_AMOUNT - REMOTE_FEE_AMOUNT_IN_WETH) / 5, + }; + let transact_info = + TransactInfo { kind: TransactKind::CallContract, params: call_params.encode() }; let xcms = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination, + destination: destination(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -569,25 +459,20 @@ fn create_agent() { }, ])); - // Send the Weth back to Ethereum ::PolkadotXcm::execute( RuntimeOrigin::signed(AssetHubWestendReceiver::get()), bx!(xcms), - Weight::from(8_000_000_000), + Weight::from(EXECUTION_WEIGHT), ) .unwrap(); }); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the transfer token back to Ethereum message was queue in the Ethereum - // Outbound Queue + // Check that Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] ); }); } - -#[test] -fn transact_with_agent() {} From 78da35b9388654dd47ae3c743e06f0ab5da14d59 Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 24 Nov 2024 10:31:23 +0800 Subject: [PATCH 23/81] Remove irrelevant changes --- .../pallets/system/src/benchmarking.rs | 65 ++++- bridges/snowbridge/pallets/system/src/lib.rs | 246 +++++++++++++++--- bridges/snowbridge/primitives/core/src/lib.rs | 10 - .../src/bridge_to_ethereum_config.rs | 4 +- 4 files changed, 280 insertions(+), 45 deletions(-) diff --git a/bridges/snowbridge/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs index e4cb079419d0..939de9d40d13 100644 --- a/bridges/snowbridge/pallets/system/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system/src/benchmarking.rs @@ -60,12 +60,74 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn create_agent() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = Location::new(1, [Parachain(origin_para_id)]); + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin); + + Ok(()) + } + + #[benchmark] + fn create_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = Location::new(1, [Parachain(origin_para_id)]); + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + + SnowbridgeControl::::create_agent(origin.clone())?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, OperatingMode::Normal); + + Ok(()) + } + + #[benchmark] + fn update_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = Location::new(1, [Parachain(origin_para_id)]); + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, OperatingMode::RejectingOutboundMessages); + + Ok(()) + } + + #[benchmark] + fn force_update_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = Location::new(1, [Parachain(origin_para_id)]); + let origin = T::Helper::make_xcm_origin(origin_location); + let channel_id: ChannelId = ParaId::from(origin_para_id).into(); + + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; + + #[extrinsic_call] + _(RawOrigin::Root, channel_id, OperatingMode::RejectingOutboundMessages); + + Ok(()) + } + #[benchmark] fn transfer_native_from_agent() -> Result<(), BenchmarkError> { let origin_para_id = 2000; let origin_location = Location::new(1, [Parachain(origin_para_id)]); let origin = T::Helper::make_xcm_origin(origin_location); fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, H160::default(), 1); @@ -77,8 +139,9 @@ mod benchmarks { fn force_transfer_native_from_agent() -> Result<(), BenchmarkError> { let origin_para_id = 2000; let origin_location = Location::new(1, [Parachain(origin_para_id)]); - let _origin = T::Helper::make_xcm_origin(origin_location.clone()); + let origin = T::Helper::make_xcm_origin(origin_location.clone()); fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; let versioned_location: VersionedLocation = origin_location.into(); diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index da927061da65..8a5b0a6edbf9 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -15,16 +15,26 @@ //! The `create_agent` extrinsic should be called via an XCM `Transact` instruction from the sibling //! parachain. //! +//! ## Channels +//! +//! Each sibling parachain has its own dedicated messaging channel for sending and receiving +//! messages. As a prerequisite to creating a channel, the sibling should have already created +//! an agent using the `create_agent` extrinsic. +//! +//! * [`Call::create_channel`]: Create channel for a sibling +//! * [`Call::update_channel`]: Update a channel for a sibling +//! //! ## Governance //! //! Only Polkadot governance itself can call these extrinsics. Delivery fees are waived. //! //! * [`Call::upgrade`]`: Upgrade the gateway contract //! * [`Call::set_operating_mode`]: Update the operating mode of the gateway contract +//! * [`Call::force_update_channel`]: Allow root to update a channel for a sibling //! * [`Call::force_transfer_native_from_agent`]: Allow root to withdraw ether from an agent //! //! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and -//! `force_update_channel` and extrinsics to manage agents for system parachains. +//! `force_update_channel` and extrinsics to manage agents and channels for system parachains. //! //! ## Polkadot-native tokens on Ethereum //! @@ -63,7 +73,6 @@ use snowbridge_core::{ v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, OperatingMode, SendError, }, - registry::{AgentRegistry, TokenRegistry}, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, @@ -93,8 +102,8 @@ fn ensure_sibling(location: &Location) -> Result<(ParaId, H256), DispatchErro where T: Config, { - match (location.parents, location.first_interior()) { - (1, Some(Parachain(para_id))) => { + match location.unpack() { + (1, [Parachain(para_id)]) => { let agent_id = agent_id_of::(location)?; Ok(((*para_id).into(), agent_id)) }, @@ -132,7 +141,7 @@ where #[frame_support::pallet] pub mod pallet { use frame_support::dispatch::PostDispatchInfo; - use snowbridge_core::StaticLookup; + use snowbridge_core::{outbound::v2::second_governance_origin, StaticLookup}; use sp_core::U256; use super::*; @@ -246,7 +255,6 @@ pub mod pallet { InvalidTokenTransferFees, InvalidPricingParameters, InvalidUpgradeParameters, - TokenAlreadyCreated, } /// The set of registered agents @@ -374,6 +382,126 @@ pub mod pallet { Ok(()) } + /// Sends a command to the Gateway contract to instantiate a new agent contract representing + /// `origin`. + /// + /// Fee required: Yes + /// + /// - `origin`: Must be `Location` of a sibling parachain + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn create_agent(origin: OriginFor) -> DispatchResult { + let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is some consensus system on a sibling parachain + let (para_id, agent_id) = ensure_sibling::(&origin_location)?; + + // Record the agent id or fail if it has already been created + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + + let command = Command::CreateAgent { agent_id }; + let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::CreateAgent { + location: Box::new(origin_location), + agent_id, + }); + Ok(()) + } + + /// Sends a message to the Gateway contract to create a new channel representing `origin` + /// + /// Fee required: Yes + /// + /// This extrinsic is permissionless, so a fee is charged to prevent spamming and pay + /// for execution costs on the remote side. + /// + /// The message is sent over the bridge on BridgeHub's own channel to the Gateway. + /// + /// - `origin`: Must be `Location` + /// - `mode`: Initial operating mode of the channel + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::create_channel())] + pub fn create_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { + let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is a sibling parachain + let (para_id, agent_id) = ensure_sibling::(&origin_location)?; + + let channel_id: ChannelId = para_id.into(); + + ensure!(Agents::::contains_key(agent_id), Error::::NoAgent); + ensure!(!Channels::::contains_key(channel_id), Error::::ChannelAlreadyCreated); + + let channel = Channel { agent_id, para_id }; + Channels::::insert(channel_id, channel); + + let command = Command::CreateChannel { channel_id, agent_id, mode }; + let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::CreateChannel { channel_id, agent_id }); + Ok(()) + } + + /// Sends a message to the Gateway contract to update a channel configuration + /// + /// The origin must already have a channel initialized, as this message is sent over it. + /// + /// A partial fee will be charged for local processing only. + /// + /// - `origin`: Must be `Location` + /// - `mode`: Initial operating mode of the channel + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::update_channel())] + pub fn update_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { + let origin_location: Location = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is a sibling parachain + let (para_id, _) = ensure_sibling::(&origin_location)?; + + let channel_id: ChannelId = para_id.into(); + + ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); + + let command = Command::UpdateChannel { channel_id, mode }; + let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); + + // Parachains send the update message on their own channel + Self::send(channel_id, command, pays_fee)?; + + Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); + Ok(()) + } + + /// Sends a message to the Gateway contract to update an arbitrary channel + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `channel_id`: ID of channel + /// - `mode`: Initial operating mode of the channel + /// - `outbound_fee`: Fee charged to users for sending outbound messages to Polkadot + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::force_update_channel())] + pub fn force_update_channel( + origin: OriginFor, + channel_id: ChannelId, + mode: OperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); + + let command = Command::UpdateChannel { channel_id, mode }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); + Ok(()) + } + /// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`. /// /// A partial fee will be charged for local processing only. @@ -513,6 +641,34 @@ pub mod pallet { pays_fee: Pays::No, }) } + + /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `location`: Location of the asset (relative to this chain) + /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::register_token())] + pub fn register_token_v2( + origin: OriginFor, + location: Box, + metadata: AssetMetadata, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let location: Location = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + Self::do_register_token_v2(&location, metadata, PaysFee::::No)?; + + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::register_token()), + pays_fee: Pays::No, + }) + } } impl Pallet { @@ -639,9 +795,44 @@ pub mod pallet { Ok(()) } - #[allow(dead_code)] + pub(crate) fn do_register_token_v2( + location: &Location, + metadata: AssetMetadata, + pays_fee: PaysFee, + ) -> Result<(), DispatchError> { + let ethereum_location = T::EthereumLocation::get(); + // reanchor to Ethereum context + let location = location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + + if !ForeignToNativeId::::contains_key(token_id) { + NativeToForeignId::::insert(location.clone(), token_id); + ForeignToNativeId::::insert(token_id, location.clone()); + } + + let command = CommandV2::RegisterForeignToken { + token_id, + name: metadata.name.into_inner(), + symbol: metadata.symbol.into_inner(), + decimals: metadata.decimals, + }; + Self::send_v2(second_governance_origin(), command, pays_fee)?; + + Self::deposit_event(Event::::RegisterToken { + location: location.clone().into(), + foreign_token_id: token_id, + }); + + Ok(()) + } + /// Send `command` to the Gateway on the Channel identified by `channel_id` - fn send_governance_command(origin: H256, command: CommandV2) -> DispatchResult { + fn send_v2(origin: H256, command: CommandV2, pays_fee: PaysFee) -> DispatchResult { let message = MessageV2 { origin, id: Default::default(), @@ -649,9 +840,23 @@ pub mod pallet { commands: BoundedVec::try_from(vec![command]).unwrap(), }; - let (ticket, _) = + let (ticket, fee) = T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; + let payment = match pays_fee { + PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee)), + PaysFee::No => None, + }; + + if let Some((payer, fee)) = payment { + T::Token::transfer( + &payer, + &T::TreasuryAccount::get(), + fee, + Preservation::Preserve, + )?; + } + T::OutboundQueueV2::deliver(ticket).map_err(|err| Error::::Send(err))?; Ok(()) } @@ -685,27 +890,4 @@ pub mod pallet { NativeToForeignId::::get(location) } } - - impl TokenRegistry for Pallet { - fn register(location: Location) -> DispatchResult { - ensure!( - NativeToForeignId::::contains_key(location.clone()), - Error::::TokenAlreadyCreated - ); - let token_id = TokenIdOf::convert_location(&location) - .ok_or(Error::::LocationConversionFailed)?; - ForeignToNativeId::::insert(token_id, location.clone()); - NativeToForeignId::::insert(location.clone(), token_id); - Ok(()) - } - } - - impl AgentRegistry for Pallet { - fn register(location: Location) -> DispatchResult { - let agent_id = agent_id_of::(&location)?; - ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); - Agents::::insert(agent_id, ()); - Ok(()) - } - } } diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 7a9a8df544d7..a4432227beef 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -56,16 +56,6 @@ impl Contains for AllowSiblingsOnly { } } -pub struct AllowAnySovereignFromSiblings; -impl Contains for AllowAnySovereignFromSiblings { - fn contains(location: &Location) -> bool { - match (location.parent_count(), location.first_interior()) { - (1, Some(Parachain(..))) => true, - _ => false, - } - } -} - pub fn gwei(x: u128) -> U256 { U256::from(1_000_000_000u128).saturating_mul(x.into()) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index ffb75a510cb5..633248ca6efb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -24,7 +24,7 @@ use crate::{ }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; -use snowbridge_core::{gwei, meth, AllowAnySovereignFromSiblings, PricingParameters, Rewards}; +use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_router_primitives::{ inbound::v1::MessageToXcm, outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, @@ -207,7 +207,7 @@ impl snowbridge_pallet_ethereum_client::Config for Runtime { impl snowbridge_pallet_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OutboundQueue = EthereumOutboundQueue; - type SiblingOrigin = EnsureXcm; + type SiblingOrigin = EnsureXcm; type AgentIdOf = snowbridge_core::AgentIdOf; type TreasuryAccount = TreasuryAccount; type Token = Balances; From 4808c36de7bbf5d99384327ed2c472f5846ddadc Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 24 Nov 2024 12:31:25 +0800 Subject: [PATCH 24/81] Validate fee asset is always in WETH --- .../pallets/outbound-queue-v2/src/api.rs | 2 +- .../pallets/outbound-queue-v2/src/lib.rs | 2 ++ .../router/src/outbound/v2/convert.rs | 29 +++++++++++++------ .../primitives/router/src/outbound/v2/mod.rs | 23 +++++++++++---- .../src/tests/snowbridge_v2.rs | 2 +- .../src/bridge_to_ethereum_config.rs | 6 ++++ 6 files changed, 48 insertions(+), 16 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 754cc59b022e..eca5312bd807 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -40,7 +40,7 @@ pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, T::Balance), DryRunEr where T: Config, { - let mut converter = XcmConverter::::new( + let mut converter = XcmConverter::::new( &xcm, T::EthereumNetwork::get(), AgentIdOf::convert_location(&Location::new(1, Parachain(1000))) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index ceeb13bfe41f..c632d18fcd64 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -139,6 +139,8 @@ pub mod pallet { type ConvertAssetId: MaybeEquivalence; type EthereumNetwork: Get; + + type WETHAddress: Get; } #[pallet::event] diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 69ee69991e9f..cfa6a6da2aa9 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -4,7 +4,7 @@ use codec::DecodeAll; use core::slice::Iter; -use frame_support::{ensure, BoundedVec}; +use frame_support::{ensure, traits::Get, BoundedVec}; use snowbridge_core::{ outbound::v2::{Command, Message}, transact::{CallContractParams, RegisterTokenParams, TransactInfo, TransactKind::*}, @@ -38,6 +38,8 @@ pub enum XcmConverterError { AliasOriginExpected, InvalidOrigin, TransactDecodeFailed, + TransactParamsDecodeFailed, + FeeAssetResolutionFailed, } macro_rules! match_expression { @@ -49,15 +51,16 @@ macro_rules! match_expression { }; } -pub struct XcmConverter<'a, ConvertAssetId, Call> { +pub struct XcmConverter<'a, ConvertAssetId, WETHAddress, Call> { iter: Peekable>>, ethereum_network: NetworkId, agent_id: AgentId, - _marker: PhantomData, + _marker: PhantomData<(ConvertAssetId, WETHAddress)>, } -impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> +impl<'a, ConvertAssetId, WETHAddress, Call> XcmConverter<'a, ConvertAssetId, WETHAddress, Call> where ConvertAssetId: MaybeEquivalence, + WETHAddress: Get, { pub fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { Self { @@ -96,12 +99,19 @@ where .ok_or(WithdrawAssetExpected)?; let fee_asset = match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; - // Todo: Validate fee asset is WETH - let fee_amount = match fee_asset { - Asset { id: _, fun: Fungible(amount) } => Some(*amount), + let (fee_asset_id, fee_amount) = match fee_asset { + Asset { id: asset_id, fun: Fungible(amount) } => Some((asset_id, *amount)), _ => None, } .ok_or(AssetResolutionFailed)?; + let weth_address = match_expression!( + fee_asset_id.0.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(FeeAssetResolutionFailed)?; + ensure!(weth_address == WETHAddress::get(), InvalidFeeAsset); Ok(fee_amount) } @@ -112,6 +122,7 @@ where /// # ReserveAssetDeposited(PNA) | WithdrawAsset(ENA) /// # AliasOrigin(Origin) /// # DepositAsset(PNA|ENA) + /// # Transact() ---Optional /// # SetTopic fn to_ethereum_message(&mut self) -> Result { use XcmConverterError::*; @@ -236,7 +247,7 @@ where RegisterAgent => commands.push(Command::CreateAgent {}), RegisterToken => { let params = RegisterTokenParams::decode_all(&mut message.params.as_slice()) - .map_err(|_| TransactDecodeFailed)?; + .map_err(|_| TransactParamsDecodeFailed)?; let token_id = TokenIdOf::convert_location(¶ms.location).ok_or(InvalidAsset)?; commands.push(Command::RegisterForeignToken { @@ -248,7 +259,7 @@ where }, CallContract => { let params = CallContractParams::decode_all(&mut message.params.as_slice()) - .map_err(|_| TransactDecodeFailed)?; + .map_err(|_| TransactParamsDecodeFailed)?; if params.value > 0 { //Todo: Ensure amount of WETH deposit to the agent in same message can // cover the value here diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs index 939a3090564e..0a29818acbf3 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs @@ -11,7 +11,7 @@ use frame_support::{ traits::{Contains, Get, ProcessMessageError}, }; use snowbridge_core::{outbound::v2::SendMessage, TokenId}; -use sp_core::H256; +use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*}; use xcm::prelude::*; @@ -26,6 +26,7 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, + WETHAddress, >( PhantomData<( UniversalLocation, @@ -33,17 +34,25 @@ pub struct EthereumBlobExporter< OutboundQueue, AgentHashedDescription, ConvertAssetId, + WETHAddress, )>, ); -impl - ExportXcm +impl< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + WETHAddress, + > ExportXcm for EthereumBlobExporter< UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription, ConvertAssetId, + WETHAddress, > where UniversalLocation: Get, @@ -51,6 +60,7 @@ where OutboundQueue: SendMessage, AgentHashedDescription: ConvertLocation, ConvertAssetId: MaybeEquivalence, + WETHAddress: Get, { type Ticket = (Vec, XcmHash); @@ -123,8 +133,11 @@ where ); ensure!(result.is_err(), SendError::NotApplicable); - let mut converter = - XcmConverter::::new(&message, expected_network, agent_id); + let mut converter = XcmConverter::::new( + &message, + expected_network, + agent_id, + ); let message = converter.convert().map_err(|err| { log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); SendError::Unroutable diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index a610ddc70bf9..c75698c2e476 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -27,7 +27,7 @@ use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 50_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; -pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +pub const WETH: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); const TOKEN_AMOUNT: u128 = 100_000_000_000; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 633248ca6efb..d9e1ff1a3d3c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -58,12 +58,17 @@ pub type SnowbridgeExporter = EthereumBlobExporter< EthereumSystem, >; +parameter_types! { + pub storage WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); +} + pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< UniversalLocation, EthereumNetwork, snowbridge_pallet_outbound_queue_v2::Pallet, snowbridge_core::AgentIdOf, EthereumSystem, + WETHAddress, >; // Ethereum Bridge @@ -143,6 +148,7 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { type RewardLedger = (); type ConvertAssetId = EthereumSystem; type EthereumNetwork = EthereumNetwork; + type WETHAddress = WETHAddress; } #[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] From 84cbb930d1effdb5cf17163916e045b611c92d8c Mon Sep 17 00:00:00 2001 From: ron Date: Sun, 24 Nov 2024 13:54:50 +0800 Subject: [PATCH 25/81] Check ether value is sufficient --- .../primitives/router/src/outbound/v2/convert.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index cfa6a6da2aa9..14f814d0db5c 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -40,6 +40,7 @@ pub enum XcmConverterError { TransactDecodeFailed, TransactParamsDecodeFailed, FeeAssetResolutionFailed, + CallContractValueInsufficient, } macro_rules! match_expression { @@ -173,6 +174,7 @@ where } let mut commands: Vec = Vec::new(); + let mut weth_amount = 0; // ENA transfer commands if let Some(enas) = enas { @@ -198,6 +200,10 @@ where // transfer amount must be greater than 0. ensure!(amount > 0, ZeroAssetTransfer); + if token == WETHAddress::get() { + weth_amount = amount; + } + commands.push(Command::UnlockNativeToken { agent_id: self.agent_id, token, @@ -261,8 +267,7 @@ where let params = CallContractParams::decode_all(&mut message.params.as_slice()) .map_err(|_| TransactParamsDecodeFailed)?; if params.value > 0 { - //Todo: Ensure amount of WETH deposit to the agent in same message can - // cover the value here + ensure!(weth_amount > params.value, CallContractValueInsufficient); } commands.push(Command::CallContract { target: params.target, From 4190bf04f94be28de7edac579bc91434c906c0a5 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 25 Nov 2024 18:29:49 +0800 Subject: [PATCH 26/81] Limited system pallet to only send_governance_call --- bridges/snowbridge/pallets/system/src/lib.rs | 24 ++++---------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 8a5b0a6edbf9..77bac014b633 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -662,7 +662,7 @@ pub mod pallet { let location: Location = (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; - Self::do_register_token_v2(&location, metadata, PaysFee::::No)?; + Self::do_register_token_v2(&location, metadata)?; Ok(PostDispatchInfo { actual_weight: Some(T::WeightInfo::register_token()), @@ -798,7 +798,6 @@ pub mod pallet { pub(crate) fn do_register_token_v2( location: &Location, metadata: AssetMetadata, - pays_fee: PaysFee, ) -> Result<(), DispatchError> { let ethereum_location = T::EthereumLocation::get(); // reanchor to Ethereum context @@ -821,7 +820,7 @@ pub mod pallet { symbol: metadata.symbol.into_inner(), decimals: metadata.decimals, }; - Self::send_v2(second_governance_origin(), command, pays_fee)?; + Self::send_governance_call(second_governance_origin(), command)?; Self::deposit_event(Event::::RegisterToken { location: location.clone().into(), @@ -831,8 +830,7 @@ pub mod pallet { Ok(()) } - /// Send `command` to the Gateway on the Channel identified by `channel_id` - fn send_v2(origin: H256, command: CommandV2, pays_fee: PaysFee) -> DispatchResult { + fn send_governance_call(origin: H256, command: CommandV2) -> DispatchResult { let message = MessageV2 { origin, id: Default::default(), @@ -840,23 +838,9 @@ pub mod pallet { commands: BoundedVec::try_from(vec![command]).unwrap(), }; - let (ticket, fee) = + let (ticket, _) = T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; - let payment = match pays_fee { - PaysFee::Yes(account) | PaysFee::Partial(account) => Some((account, fee)), - PaysFee::No => None, - }; - - if let Some((payer, fee)) = payment { - T::Token::transfer( - &payer, - &T::TreasuryAccount::get(), - fee, - Preservation::Preserve, - )?; - } - T::OutboundQueueV2::deliver(ticket).map_err(|err| Error::::Send(err))?; Ok(()) } From 178f50a3125d382c78dc41fbf84a462faf56c6d6 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 26 Nov 2024 11:40:09 +0800 Subject: [PATCH 27/81] Remove agent_id from converter --- .../pallets/outbound-queue-v2/src/api.rs | 27 ++++++------------- .../pallets/outbound-queue-v2/src/lib.rs | 16 ++++++++++- bridges/snowbridge/pallets/system/src/lib.rs | 24 +++++++++++++++++ .../primitives/core/src/location.rs | 10 +++++-- .../primitives/core/src/outbound/v2.rs | 5 ++-- .../primitives/core/src/registry.rs | 8 +++--- .../router/src/outbound/v2/convert.rs | 18 +++++-------- .../primitives/router/src/outbound/v2/mod.rs | 19 +++---------- .../src/tests/snowbridge_v2.rs | 2 +- .../src/bridge_to_ethereum_config.rs | 1 + 10 files changed, 72 insertions(+), 58 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index eca5312bd807..2912705dd151 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -4,25 +4,18 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; -use snowbridge_core::{ - outbound::{ - v2::{ - abi::{CommandWrapper, InboundMessage}, - GasMeter, Message, - }, - DryRunError, +use snowbridge_core::outbound::{ + v2::{ + abi::{CommandWrapper, InboundMessage}, + GasMeter, Message, }, - AgentIdOf, + DryRunError, }; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; use snowbridge_router_primitives::outbound::v2::convert::XcmConverter; use sp_core::Get; use sp_std::{default::Default, vec::Vec}; -use xcm::{ - latest::Location, - prelude::{Parachain, Xcm}, -}; -use xcm_executor::traits::ConvertLocation; +use xcm::prelude::Xcm; pub fn prove_message(leaf_index: u64) -> Option where @@ -40,12 +33,8 @@ pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, T::Balance), DryRunEr where T: Config, { - let mut converter = XcmConverter::::new( - &xcm, - T::EthereumNetwork::get(), - AgentIdOf::convert_location(&Location::new(1, Parachain(1000))) - .ok_or(DryRunError::ConvertLocationFailed)?, - ); + let mut converter = + XcmConverter::::new(&xcm, T::EthereumNetwork::get()); let message: Message = converter.convert().map_err(|_| DryRunError::ConvertXcmFailed)?; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index c632d18fcd64..88772d2e3aa0 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -75,8 +75,9 @@ use snowbridge_core::{ inbound::{Message as DeliveryMessage, VerificationError, Verifier}, outbound::v2::{ abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, - GasMeter, Message, + Command, GasMeter, Message, }, + registry::Registry, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; @@ -141,6 +142,8 @@ pub mod pallet { type EthereumNetwork: Get; type WETHAddress: Get; + + type Registry: Registry; } #[pallet::event] @@ -331,8 +334,11 @@ pub mod pallet { let nonce = Nonce::::get(); + let original_location = message.origin_location; + let commands: Vec = message .commands + .clone() .into_iter() .map(|command| CommandWrapper { kind: command.index(), @@ -370,6 +376,14 @@ pub mod pallet { Nonce::::set(nonce.checked_add(1).ok_or(Unsupported)?); + for command in message.commands.into_iter() { + match command { + Command::CreateAgent {} => + T::Registry::register_agent(&original_location).map_err(|_| Corrupt)?, + _ => (), + } + } + Self::deposit_event(Event::MessageAccepted { id: message.id, nonce }); Ok(true) diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 77bac014b633..5ce1f00cd468 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -73,6 +73,7 @@ use snowbridge_core::{ v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, OperatingMode, SendError, }, + registry::Registry, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, @@ -255,6 +256,7 @@ pub mod pallet { InvalidTokenTransferFees, InvalidPricingParameters, InvalidUpgradeParameters, + TokenAlreadyCreated, } /// The set of registered agents @@ -833,6 +835,7 @@ pub mod pallet { fn send_governance_call(origin: H256, command: CommandV2) -> DispatchResult { let message = MessageV2 { origin, + origin_location: Default::default(), id: Default::default(), fee: Default::default(), commands: BoundedVec::try_from(vec![command]).unwrap(), @@ -874,4 +877,25 @@ pub mod pallet { NativeToForeignId::::get(location) } } + + impl Registry for pallet::Pallet { + fn register_agent(location: &Location) -> DispatchResult { + let agent_id = agent_id_of::(&location)?; + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + Ok(()) + } + + fn register_token(location: &Location) -> DispatchResult { + ensure!( + NativeToForeignId::::contains_key(location.clone()), + Error::::TokenAlreadyCreated + ); + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + ForeignToNativeId::::insert(token_id, location.clone()); + NativeToForeignId::::insert(location.clone(), token_id); + Ok(()) + } + } } diff --git a/bridges/snowbridge/primitives/core/src/location.rs b/bridges/snowbridge/primitives/core/src/location.rs index f49a245c4126..4940fb229c60 100644 --- a/bridges/snowbridge/primitives/core/src/location.rs +++ b/bridges/snowbridge/primitives/core/src/location.rs @@ -24,8 +24,14 @@ pub type AgentId = H256; /// Creates an AgentId from a Location. An AgentId is a unique mapping to a Agent contract on /// Ethereum which acts as the sovereign account for the Location. #[allow(deprecated)] -pub type AgentIdOf = - HashedDescription)>; +pub type AgentIdOf = HashedDescription< + AgentId, + ( + DescribeHere, + DescribeFamily, + DescribeGlobalPrefix<(DescribeTerminus, DescribeFamily)>, + ), +>; pub type TokenId = H256; diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/core/src/outbound/v2.rs index e9ff47786363..a45fcc9eb261 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/core/src/outbound/v2.rs @@ -17,6 +17,7 @@ use crate::outbound::v2::abi::{ }; use alloy_primitives::{Address, FixedBytes, U256}; use alloy_sol_types::SolValue; +use xcm::prelude::Location; pub mod abi { use super::MAX_COMMANDS; @@ -119,6 +120,8 @@ pub const MAX_COMMANDS: u32 = 8; /// A message which can be accepted by implementations of `/[`SendMessage`\]` #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] pub struct Message { + /// Origin Location + pub origin_location: Location, /// Origin pub origin: H256, /// ID @@ -150,8 +153,6 @@ pub enum Command { }, /// Unlock ERC20 tokens UnlockNativeToken { - /// ID of the agent - agent_id: H256, /// Address of the ERC20 token token: H160, /// The recipient of the tokens diff --git a/bridges/snowbridge/primitives/core/src/registry.rs b/bridges/snowbridge/primitives/core/src/registry.rs index f3b87bdbbfac..da7e2738905e 100644 --- a/bridges/snowbridge/primitives/core/src/registry.rs +++ b/bridges/snowbridge/primitives/core/src/registry.rs @@ -4,10 +4,8 @@ use frame_support::dispatch::DispatchResult; use xcm::prelude::Location; -pub trait TokenRegistry { - fn register(location: Location) -> DispatchResult; -} +pub trait Registry { + fn register_agent(location: &Location) -> DispatchResult; -pub trait AgentRegistry { - fn register(location: Location) -> DispatchResult; + fn register_token(location: &Location) -> DispatchResult; } diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 14f814d0db5c..00382ba7c349 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -8,7 +8,7 @@ use frame_support::{ensure, traits::Get, BoundedVec}; use snowbridge_core::{ outbound::v2::{Command, Message}, transact::{CallContractParams, RegisterTokenParams, TransactInfo, TransactKind::*}, - AgentId, TokenId, TokenIdOf, TokenIdOf as LocationIdOf, + TokenId, TokenIdOf, TokenIdOf as LocationIdOf, }; use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; @@ -55,7 +55,6 @@ macro_rules! match_expression { pub struct XcmConverter<'a, ConvertAssetId, WETHAddress, Call> { iter: Peekable>>, ethereum_network: NetworkId, - agent_id: AgentId, _marker: PhantomData<(ConvertAssetId, WETHAddress)>, } impl<'a, ConvertAssetId, WETHAddress, Call> XcmConverter<'a, ConvertAssetId, WETHAddress, Call> @@ -63,11 +62,10 @@ where ConvertAssetId: MaybeEquivalence, WETHAddress: Get, { - pub fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { + pub fn new(message: &'a Xcm, ethereum_network: NetworkId) -> Self { Self { iter: message.inner().iter().peekable(), ethereum_network, - agent_id, _marker: Default::default(), } } @@ -148,9 +146,9 @@ where let _ = self.next(); } // Check AliasOrigin. - let origin_loc = match_expression!(self.next()?, AliasOrigin(origin), origin) + let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin) .ok_or(AliasOriginExpected)?; - let origin = LocationIdOf::convert_location(&origin_loc).ok_or(InvalidOrigin)?; + let origin = LocationIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?; let (deposit_assets, beneficiary) = match_expression!( self.next()?, @@ -204,12 +202,7 @@ where weth_amount = amount; } - commands.push(Command::UnlockNativeToken { - agent_id: self.agent_id, - token, - recipient, - amount, - }); + commands.push(Command::UnlockNativeToken { token, recipient, amount }); } } @@ -284,6 +277,7 @@ where let message = Message { id: (*topic_id).into(), + origin_location: origin_location.clone(), origin, fee: fee_amount, commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?, diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs index 0a29818acbf3..b52315f0add1 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs @@ -89,7 +89,7 @@ where } // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, local_sub) = universal_source.clone() + let (local_net, _) = universal_source.clone() .ok_or_else(|| { log::error!(target: TARGET, "universal source not provided."); SendError::MissingArgument @@ -105,16 +105,6 @@ where return Err(SendError::NotApplicable) } - let source_location = Location::new(1, local_sub.clone()); - - let agent_id = match AgentHashedDescription::convert_location(&source_location) { - Some(id) => id, - None => { - log::error!(target: TARGET, "unroutable due to not being able to create agent id. '{source_location:?}'"); - return Err(SendError::NotApplicable) - }, - }; - let message = message.clone().ok_or_else(|| { log::error!(target: TARGET, "xcm message not provided."); SendError::MissingArgument @@ -133,11 +123,8 @@ where ); ensure!(result.is_err(), SendError::NotApplicable); - let mut converter = XcmConverter::::new( - &message, - expected_network, - agent_id, - ); + let mut converter = + XcmConverter::::new(&message, expected_network); let message = converter.convert().map_err(|err| { log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); SendError::Unroutable diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index c75698c2e476..f639fb6c3b22 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -388,7 +388,7 @@ fn create_agent() { // Check that Ethereum message was queue in the Outbound Queue assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageAccepted{ .. }) => {},] ); }); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index d9e1ff1a3d3c..b25e659a1968 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -149,6 +149,7 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { type ConvertAssetId = EthereumSystem; type EthereumNetwork = EthereumNetwork; type WETHAddress = WETHAddress; + type Registry = EthereumSystem; } #[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] From aba918d8fd6ceaa23fd92b1fb64173a33de4cb31 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 00:08:54 +0800 Subject: [PATCH 28/81] Fix breaking tests --- .../pallets/outbound-queue-v2/src/lib.rs | 15 +- .../pallets/outbound-queue-v2/src/mock.rs | 9 +- .../pallets/outbound-queue-v2/src/test.rs | 1 + .../router/src/outbound/v2/convert.rs | 184 +++++++++++++----- .../primitives/router/src/outbound/v2/mod.rs | 30 ++- .../src/bridge_to_ethereum_config.rs | 1 - 6 files changed, 162 insertions(+), 78 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 88772d2e3aa0..80309d530baf 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -75,9 +75,8 @@ use snowbridge_core::{ inbound::{Message as DeliveryMessage, VerificationError, Verifier}, outbound::v2::{ abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, - Command, GasMeter, Message, + GasMeter, Message, }, - registry::Registry, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; @@ -142,8 +141,6 @@ pub mod pallet { type EthereumNetwork: Get; type WETHAddress: Get; - - type Registry: Registry; } #[pallet::event] @@ -334,8 +331,6 @@ pub mod pallet { let nonce = Nonce::::get(); - let original_location = message.origin_location; - let commands: Vec = message .commands .clone() @@ -376,14 +371,6 @@ pub mod pallet { Nonce::::set(nonce.checked_add(1).ok_or(Unsupported)?); - for command in message.commands.into_iter() { - match command { - Command::CreateAgent {} => - T::Registry::register_agent(&original_location).map_err(|_| Corrupt)?, - _ => (), - } - } - Self::deposit_event(Event::MessageAccepted { id: message.id, nonce }); Ok(true) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs index 353747b23a5f..2215f388b70d 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs @@ -83,8 +83,6 @@ impl Verifier for MockVerifier { const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; const WETH: [u8; 20] = hex!["C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"]; -const ASSET_HUB_AGENT: [u8; 32] = - hex!["81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79"]; parameter_types! { pub const OwnParaId: ParaId = ParaId::new(1013); @@ -96,7 +94,7 @@ parameter_types! { }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; - + pub storage WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); } pub const DOT: u128 = 10_000_000_000; @@ -115,6 +113,7 @@ impl crate::Config for Test { type RewardLedger = (); type ConvertAssetId = (); type EthereumNetwork = EthereumNetwork; + type WETHAddress = WETHAddress; } fn setup() { @@ -151,6 +150,7 @@ where let _marker = PhantomData::; // for clippy Message { + origin_location: Default::default(), origin: primary_governance_origin(), id: Default::default(), fee: 0, @@ -171,6 +171,7 @@ where let _marker = PhantomData::; // for clippy Message { + origin_location: Default::default(), origin: Default::default(), id: Default::default(), fee: 0, @@ -188,11 +189,11 @@ where pub fn mock_message(sibling_para_id: u32) -> Message { Message { + origin_location: Default::default(), origin: H256::from_low_u64_be(sibling_para_id as u64), id: H256::from_low_u64_be(1), fee: 1_000, commands: BoundedVec::try_from(vec![Command::UnlockNativeToken { - agent_id: H256(ASSET_HUB_AGENT), token: H160(WETH), recipient: H160(GATEWAY_ADDRESS), amount: 1_000_000, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index 0c1c2868cb90..abbbfd64f54a 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -71,6 +71,7 @@ fn process_message_yields_on_max_messages_per_block() { let _channel_id: ChannelId = ParaId::from(1000).into(); let origin = AggregateMessageOrigin::SnowbridgeV2(H256::zero()); let message = Message { + origin_location: Default::default(), origin: Default::default(), id: Default::default(), fee: 0, diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index 00382ba7c349..d98bf1686bce 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -295,11 +295,12 @@ where #[cfg(test)] mod tests { use super::*; - use crate::outbound::v2::tests::{BridgedNetwork, MockTokenIdConvert, NonBridgedNetwork}; + use crate::outbound::v2::tests::{ + BridgedNetwork, MockTokenIdConvert, NonBridgedNetwork, WETHAddress, + }; use hex_literal::hex; use snowbridge_core::AgentIdOf; - use sp_std::default::Default; - use xcm::latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}; + use xcm::latest::WESTEND_GENESIS_HASH; #[test] fn xcm_converter_convert_success() { @@ -315,9 +316,15 @@ mod tests { .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -328,7 +335,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert!(result.is_ok()); } @@ -346,10 +353,15 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(All); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -360,7 +372,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.is_ok(), true); } @@ -378,10 +390,15 @@ mod tests { }] .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -392,13 +409,13 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); } #[test] - fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { + fn xcm_converter_convert_with_partial_message_yields_invalid_fee_asset() { let network = BridgedNetwork::get(); let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); @@ -410,7 +427,7 @@ mod tests { let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); } @@ -423,13 +440,15 @@ mod tests { let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); let asset_location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = - Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; - let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), @@ -444,7 +463,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.is_ok(), true); } @@ -457,7 +476,11 @@ mod tests { let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); @@ -477,7 +500,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.is_ok(), true); } @@ -489,7 +512,7 @@ mod tests { let message: Xcm<()> = vec![].into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); @@ -508,10 +531,15 @@ mod tests { }] .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -523,7 +551,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); @@ -554,7 +582,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); @@ -572,16 +600,22 @@ mod tests { }] .into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), SetTopic([0; 32]), ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); @@ -591,22 +625,20 @@ mod tests { fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { let network = BridgedNetwork::get(); - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); let assets: Assets = vec![].into(); let filter: AssetFilter = assets.clone().into(); - let fee = Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), fun: Fungible(1000), - }; + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: fee.clone() }, - WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { assets: filter, @@ -616,7 +648,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); @@ -642,10 +674,15 @@ mod tests { ] .into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -656,7 +693,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.is_ok(), true); @@ -675,10 +712,15 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -689,7 +731,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); @@ -708,10 +750,15 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -722,7 +769,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); @@ -740,10 +787,14 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone().into()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -754,7 +805,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -775,10 +826,14 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone().into()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -789,7 +844,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -811,10 +866,14 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone().into()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -825,7 +884,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); @@ -846,9 +905,14 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone().into()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -860,7 +924,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); @@ -880,10 +944,14 @@ mod tests { }] .into(); let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -898,7 +966,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); @@ -943,10 +1011,14 @@ mod tests { let assets: Assets = vec![Asset { id: AssetId(asset_location.clone()), fun: Fungible(amount) }].into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, ReserveAssetDeposited(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -957,13 +1029,14 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let expected_payload = Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; let expected_message = Message { + origin_location: Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)]), id: [0; 32].into(), origin: hex!("aa16eddac8725928eaeda4aae518bf10d02bee80382517d21464a5cdf8d1d8e1").into(), - fee: 1000000, + fee: 1000, commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), }; let result = converter.convert(); @@ -980,16 +1053,21 @@ mod tests { // Invalid asset location from a different consensus let asset_location = Location { parents: 2, - interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into(), + interior: [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))].into(), }; let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + let message: Xcm<()> = vec![ WithdrawAsset(assets.clone()), - PayFees { asset: assets.get(0).unwrap().clone() }, + PayFees { asset: fee_asset }, ReserveAssetDeposited(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -1000,7 +1078,7 @@ mod tests { ] .into(); let mut converter = - XcmConverter::::new(&message, network, Default::default()); + XcmConverter::::new(&message, network); let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); } diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs index b52315f0add1..0fbfc2784efa 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs @@ -203,10 +203,7 @@ mod tests { AgentIdOf, }; use sp_std::default::Default; - use xcm::{ - latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, - prelude::SendError as XcmSendError, - }; + use xcm::{latest::WESTEND_GENESIS_HASH, prelude::SendError as XcmSendError}; parameter_types! { const MaxMessageSize: u32 = u32::MAX; @@ -214,6 +211,7 @@ mod tests { UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); pub const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; pub const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + pub WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); } struct MockOkOutboundQueue; @@ -286,6 +284,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -305,6 +304,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -330,6 +330,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -349,6 +350,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -368,6 +370,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -387,6 +390,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -407,6 +411,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -426,6 +431,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -446,6 +452,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -466,6 +473,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -514,6 +522,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); @@ -542,6 +551,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); @@ -564,13 +574,17 @@ mod tests { fun: Fungible(1000), }] .into(); - let fee = assets.clone().get(0).unwrap().clone(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); let filter: AssetFilter = assets.clone().into(); let mut message: Option> = Some( vec![ WithdrawAsset(assets.clone()), - PayFees { asset: fee.clone() }, + PayFees { asset: fee_asset }, WithdrawAsset(assets.clone()), AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), DepositAsset { @@ -589,6 +603,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert!(result.is_ok()); @@ -602,6 +617,7 @@ mod tests { MockErrOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) } @@ -646,6 +662,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate( network, channel, @@ -702,6 +719,7 @@ mod tests { MockOkOutboundQueue, AgentIdOf, MockTokenIdConvert, + WETHAddress, >::validate( network, channel, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index b25e659a1968..d9e1ff1a3d3c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -149,7 +149,6 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { type ConvertAssetId = EthereumSystem; type EthereumNetwork = EthereumNetwork; type WETHAddress = WETHAddress; - type Registry = EthereumSystem; } #[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))] From a7ced85f713cf438c6916a045883648663a1a9a3 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 00:45:49 +0800 Subject: [PATCH 29/81] Clean up --- bridges/snowbridge/pallets/system/src/lib.rs | 22 --- bridges/snowbridge/primitives/core/src/lib.rs | 2 - .../primitives/core/src/outbound/mod.rs | 11 +- .../primitives/core/src/registry.rs | 11 -- .../primitives/core/src/transact.rs | 36 ----- .../router/src/outbound/v2/convert.rs | 43 ++---- .../src/tests/snowbridge_v2.rs | 139 +++++++++--------- 7 files changed, 90 insertions(+), 174 deletions(-) delete mode 100644 bridges/snowbridge/primitives/core/src/registry.rs delete mode 100644 bridges/snowbridge/primitives/core/src/transact.rs diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 5ce1f00cd468..e603e562201f 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -73,7 +73,6 @@ use snowbridge_core::{ v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, OperatingMode, SendError, }, - registry::Registry, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, @@ -877,25 +876,4 @@ pub mod pallet { NativeToForeignId::::get(location) } } - - impl Registry for pallet::Pallet { - fn register_agent(location: &Location) -> DispatchResult { - let agent_id = agent_id_of::(&location)?; - ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); - Agents::::insert(agent_id, ()); - Ok(()) - } - - fn register_token(location: &Location) -> DispatchResult { - ensure!( - NativeToForeignId::::contains_key(location.clone()), - Error::::TokenAlreadyCreated - ); - let token_id = TokenIdOf::convert_location(&location) - .ok_or(Error::::LocationConversionFailed)?; - ForeignToNativeId::::insert(token_id, location.clone()); - NativeToForeignId::::insert(location.clone(), token_id); - Ok(()) - } - } } diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index a4432227beef..88ac8124a15b 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -13,10 +13,8 @@ pub mod location; pub mod operating_mode; pub mod outbound; pub mod pricing; -pub mod registry; pub mod reward; pub mod ringbuffer; -pub mod transact; pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; pub use polkadot_parachain_primitives::primitives::{ diff --git a/bridges/snowbridge/primitives/core/src/outbound/mod.rs b/bridges/snowbridge/primitives/core/src/outbound/mod.rs index 0aa60f479195..972f16fb2139 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/core/src/outbound/mod.rs @@ -3,11 +3,12 @@ //! # Outbound //! //! Common traits and types +use crate::Vec; use codec::{Decode, Encode}; use frame_support::PalletError; use scale_info::TypeInfo; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; -use sp_core::RuntimeDebug; +use sp_core::{RuntimeDebug, H160}; pub mod v1; pub mod v2; @@ -47,3 +48,11 @@ pub enum DryRunError { ConvertLocationFailed, ConvertXcmFailed, } + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct TransactInfo { + pub target: H160, + pub data: Vec, + pub gas_limit: u64, + pub value: u128, +} diff --git a/bridges/snowbridge/primitives/core/src/registry.rs b/bridges/snowbridge/primitives/core/src/registry.rs deleted file mode 100644 index da7e2738905e..000000000000 --- a/bridges/snowbridge/primitives/core/src/registry.rs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork - -use frame_support::dispatch::DispatchResult; -use xcm::prelude::Location; - -pub trait Registry { - fn register_agent(location: &Location) -> DispatchResult; - - fn register_token(location: &Location) -> DispatchResult; -} diff --git a/bridges/snowbridge/primitives/core/src/transact.rs b/bridges/snowbridge/primitives/core/src/transact.rs deleted file mode 100644 index 0dc77555ae45..000000000000 --- a/bridges/snowbridge/primitives/core/src/transact.rs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork - -use crate::{AssetMetadata, Vec}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_core::H160; -use sp_runtime::RuntimeDebug; -use xcm::prelude::Location; - -#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] -pub struct TransactInfo { - pub kind: TransactKind, - pub params: Vec, -} - -#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] -pub enum TransactKind { - RegisterToken, - RegisterAgent, - CallContract, -} - -#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] -pub struct RegisterTokenParams { - pub location: Location, - pub metadata: AssetMetadata, -} - -#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] -pub struct CallContractParams { - pub target: H160, - pub data: Vec, - pub gas_limit: u64, - pub value: u128, -} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs index d98bf1686bce..77616bde2796 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs @@ -6,8 +6,10 @@ use codec::DecodeAll; use core::slice::Iter; use frame_support::{ensure, traits::Get, BoundedVec}; use snowbridge_core::{ - outbound::v2::{Command, Message}, - transact::{CallContractParams, RegisterTokenParams, TransactInfo, TransactKind::*}, + outbound::{ + v2::{Command, Message}, + TransactInfo, + }, TokenId, TokenIdOf, TokenIdOf as LocationIdOf, }; use sp_core::H160; @@ -239,37 +241,18 @@ where let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call); if let Some(transact_call) = transact_call { let _ = self.next(); - let message = + let transact = TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) .map_err(|_| TransactDecodeFailed)?; - match message.kind { - RegisterAgent => commands.push(Command::CreateAgent {}), - RegisterToken => { - let params = RegisterTokenParams::decode_all(&mut message.params.as_slice()) - .map_err(|_| TransactParamsDecodeFailed)?; - let token_id = - TokenIdOf::convert_location(¶ms.location).ok_or(InvalidAsset)?; - commands.push(Command::RegisterForeignToken { - token_id, - name: params.metadata.name.into_inner(), - symbol: params.metadata.symbol.into_inner(), - decimals: params.metadata.decimals, - }); - }, - CallContract => { - let params = CallContractParams::decode_all(&mut message.params.as_slice()) - .map_err(|_| TransactParamsDecodeFailed)?; - if params.value > 0 { - ensure!(weth_amount > params.value, CallContractValueInsufficient); - } - commands.push(Command::CallContract { - target: params.target, - data: params.data, - gas_limit: params.gas_limit, - value: params.value, - }); - }, + if transact.value > 0 { + ensure!(weth_amount > transact.value, CallContractValueInsufficient); } + commands.push(Command::CallContract { + target: transact.target, + data: transact.data, + gas_limit: transact.gas_limit, + value: transact.value, + }); } // ensure SetTopic exists diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index f639fb6c3b22..b07f7faf554c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -15,10 +15,7 @@ use crate::imports::*; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; -use snowbridge_core::{ - transact::{CallContractParams, TransactInfo, TransactKind}, - AssetMetadata, -}; +use snowbridge_core::{outbound::TransactInfo, AssetMetadata}; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use sp_runtime::MultiAddress; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; @@ -327,71 +324,71 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { }); } -#[test] -fn create_agent() { - fund_sovereign(); - - register_weth(); - - BridgeHubWestend::execute_with(|| {}); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - let local_fee_asset = - Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; - - // All WETH as fee and reserve_asset is zero, so there is no transfer in this case - let remote_fee_asset = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) }; - let reserve_asset = Asset { id: AssetId(weth_location()), fun: Fungible(0) }; - - let assets = vec![ - Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }, - local_fee_asset.clone(), - ]; - - let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; - - let xcms = VersionedXcm::from(Xcm(vec![ - WithdrawAsset(assets.clone().into()), - PayFees { asset: local_fee_asset.clone() }, - InitiateTransfer { - destination: destination(), - remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( - remote_fee_asset.clone().into(), - ))), - preserve_origin: true, - assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( - reserve_asset.clone().into(), - ))], - remote_xcm: Xcm(vec![ - DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary() }, - Transact { - origin_kind: OriginKind::SovereignAccount, - call: transact_info.encode().into(), - }, - ]), - }, - ])); - - // Send the Weth back to Ethereum - ::PolkadotXcm::execute( - RuntimeOrigin::signed(AssetHubWestendReceiver::get()), - bx!(xcms), - Weight::from(8_000_000_000), - ) - .unwrap(); - }); - - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - // Check that Ethereum message was queue in the Outbound Queue - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageAccepted{ .. }) => {},] - ); - }); -} +// #[test] +// fn create_agent() { +// fund_sovereign(); +// +// register_weth(); +// +// BridgeHubWestend::execute_with(|| {}); +// +// AssetHubWestend::execute_with(|| { +// type RuntimeOrigin = ::RuntimeOrigin; +// +// let local_fee_asset = +// Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; +// +// // All WETH as fee and reserve_asset is zero, so there is no transfer in this case +// let remote_fee_asset = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) }; +// let reserve_asset = Asset { id: AssetId(weth_location()), fun: Fungible(0) }; +// +// let assets = vec![ +// Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }, +// local_fee_asset.clone(), +// ]; +// +// let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; +// +// let xcms = VersionedXcm::from(Xcm(vec![ +// WithdrawAsset(assets.clone().into()), +// PayFees { asset: local_fee_asset.clone() }, +// InitiateTransfer { +// destination: destination(), +// remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( +// remote_fee_asset.clone().into(), +// ))), +// preserve_origin: true, +// assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( +// reserve_asset.clone().into(), +// ))], +// remote_xcm: Xcm(vec![ +// DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary() }, +// Transact { +// origin_kind: OriginKind::SovereignAccount, +// call: transact_info.encode().into(), +// }, +// ]), +// }, +// ])); +// +// // Send the Weth back to Ethereum +// ::PolkadotXcm::execute( +// RuntimeOrigin::signed(AssetHubWestendReceiver::get()), +// bx!(xcms), +// Weight::from(8_000_000_000), +// ) +// .unwrap(); +// }); +// +// BridgeHubWestend::execute_with(|| { +// type RuntimeEvent = ::RuntimeEvent; +// // Check that Ethereum message was queue in the Outbound Queue +// assert_expected_events!( +// BridgeHubWestend, +// vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageAccepted{ .. }) => {},] +// ); +// }); +// } #[test] fn transact_with_agent() { @@ -427,15 +424,13 @@ fn transact_with_agent() { let beneficiary = Location::new(0, [AccountKey20 { network: None, key: AGENT_ADDRESS.into() }]); - let call_params = CallContractParams { + let transact_info = TransactInfo { target: Default::default(), data: vec![], gas_limit: 40000, // value should be less than the transfer amount, require validation on BH Exporter value: 4 * (TOKEN_AMOUNT - REMOTE_FEE_AMOUNT_IN_WETH) / 5, }; - let transact_info = - TransactInfo { kind: TransactKind::CallContract, params: call_params.encode() }; let xcms = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), From 5696fdfb7f24b2b069c838225c3e9f9247ab4c7c Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 02:39:19 +0800 Subject: [PATCH 30/81] Seperate outbound router crates --- Cargo.lock | 25 +- Cargo.toml | 2 + .../pallets/outbound-queue-v2/Cargo.toml | 6 +- .../pallets/outbound-queue-v2/src/api.rs | 2 +- .../pallets/outbound-queue-v2/src/lib.rs | 2 +- .../pallets/outbound-queue/src/lib.rs | 2 +- .../primitives/outbound-router/Cargo.toml | 57 + .../primitives/outbound-router/README.md | 4 + .../mod.rs => outbound-router/src/lib.rs} | 3 +- .../primitives/outbound-router/src/v1/mod.rs | 423 ++++ .../outbound-router/src/v1/tests.rs | 1274 ++++++++++++ .../outbound-router/src/v2/convert.rs | 276 +++ .../primitives/outbound-router/src/v2/mod.rs | 197 ++ .../outbound-router/src/v2/tests.rs | 1288 +++++++++++++ .../snowbridge/primitives/router/src/lib.rs | 1 - .../primitives/router/src/outbound/v1/mod.rs | 1703 ----------------- .../router/src/outbound/v2/convert.rs | 1068 ----------- .../primitives/router/src/outbound/v2/mod.rs | 738 ------- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../assets/asset-hub-westend/Cargo.toml | 3 + .../asset-hub-westend/src/xcm_config.rs | 4 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 3 + .../src/bridge_to_ethereum_config.rs | 3 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 3 + .../src/bridge_to_ethereum_config.rs | 6 +- 25 files changed, 3570 insertions(+), 3524 deletions(-) create mode 100644 bridges/snowbridge/primitives/outbound-router/Cargo.toml create mode 100644 bridges/snowbridge/primitives/outbound-router/README.md rename bridges/snowbridge/primitives/{router/src/outbound/mod.rs => outbound-router/src/lib.rs} (65%) create mode 100644 bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs create mode 100644 bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs create mode 100644 bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs create mode 100644 bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs create mode 100644 bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs delete mode 100644 bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs delete mode 100644 bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs delete mode 100644 bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 09576d599ca1..25877bbd36bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1044,6 +1044,7 @@ dependencies = [ "primitive-types 0.13.1", "scale-info", "serde_json", + "snowbridge-outbound-router-primitives", "snowbridge-router-primitives 0.9.0", "sp-api 26.0.0", "sp-block-builder 26.0.0", @@ -2594,6 +2595,7 @@ dependencies = [ "snowbridge-core 0.2.0", "snowbridge-merkle-tree", "snowbridge-outbound-queue-runtime-api 0.2.0", + "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", "snowbridge-pallet-outbound-queue 0.2.0", @@ -2751,6 +2753,7 @@ dependencies = [ "rococo-westend-system-emulated-network", "scale-info", "snowbridge-core 0.2.0", + "snowbridge-outbound-router-primitives", "snowbridge-pallet-inbound-queue 0.2.0", "snowbridge-pallet-inbound-queue-fixtures 0.10.0", "snowbridge-pallet-outbound-queue 0.2.0", @@ -2834,6 +2837,7 @@ dependencies = [ "snowbridge-merkle-tree", "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-outbound-queue-runtime-api-v2", + "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", "snowbridge-pallet-outbound-queue 0.2.0", @@ -24900,6 +24904,25 @@ dependencies = [ "staging-xcm 7.0.0", ] +[[package]] +name = "snowbridge-outbound-router-primitives" +version = "0.9.0" +dependencies = [ + "frame-support 28.0.0", + "hex-literal", + "log", + "parity-scale-codec", + "scale-info", + "snowbridge-core 0.2.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", +] + [[package]] name = "snowbridge-pallet-ethereum-client" version = "0.2.0" @@ -25121,7 +25144,7 @@ dependencies = [ "serde", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", - "snowbridge-router-primitives 0.9.0", + "snowbridge-outbound-router-primitives", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", diff --git a/Cargo.toml b/Cargo.toml index 86aa6c5c31f2..b753c867b51e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "bridges/snowbridge/primitives/core", "bridges/snowbridge/primitives/ethereum", "bridges/snowbridge/primitives/merkle-tree", + "bridges/snowbridge/primitives/outbound-router", "bridges/snowbridge/primitives/router", "bridges/snowbridge/runtime/runtime-common", "bridges/snowbridge/runtime/test-common", @@ -1227,6 +1228,7 @@ snowbridge-ethereum = { path = "bridges/snowbridge/primitives/ethereum", default snowbridge-merkle-tree = { path = "bridges/snowbridge/primitives/merkle-tree", default-features = false } snowbridge-outbound-queue-runtime-api = { path = "bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } snowbridge-outbound-queue-runtime-api-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", default-features = false } +snowbridge-outbound-router-primitives = { path = "bridges/snowbridge/primitives/outbound-router", default-features = false } snowbridge-pallet-ethereum-client = { path = "bridges/snowbridge/pallets/ethereum-client", default-features = false } snowbridge-pallet-ethereum-client-fixtures = { path = "bridges/snowbridge/pallets/ethereum-client/fixtures", default-features = false } snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-queue", default-features = false } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml index 560192c759f8..ac8dee02f116 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml @@ -36,7 +36,7 @@ snowbridge-core = { features = ["serde"], workspace = true } ethabi = { workspace = true } hex-literal = { workspace = true, default-features = true } snowbridge-merkle-tree = { workspace = true } -snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-router-primitives = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } xcm-builder = { workspace = true } @@ -61,7 +61,7 @@ std = [ "serde/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", - "snowbridge-router-primitives/std", + "snowbridge-outbound-router-primitives/std", "sp-arithmetic/std", "sp-core/std", "sp-io/std", @@ -79,7 +79,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", - "snowbridge-router-primitives/runtime-benchmarks", + "snowbridge-outbound-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 2912705dd151..75e51be90112 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -12,7 +12,7 @@ use snowbridge_core::outbound::{ DryRunError, }; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; -use snowbridge_router_primitives::outbound::v2::convert::XcmConverter; +use snowbridge_outbound_router_primitives::v2::convert::XcmConverter; use sp_core::Get; use sp_std::{default::Default, vec::Vec}; use xcm::prelude::Xcm; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 80309d530baf..6b669a75e5c9 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -7,7 +7,7 @@ //! Messages come either from sibling parachains via XCM, or BridgeHub itself //! via the `snowbridge-pallet-system`: //! -//! 1. `snowbridge_router_primitives::outbound::v2::EthereumBlobExporter::deliver` +//! 1. `snowbridge_outbound_router_primitives::v2::EthereumBlobExporter::deliver` //! 2. `snowbridge_pallet_system::Pallet::send_v2` //! //! The message submission pipeline works like this: diff --git a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs index 0d43519167af..feb86bce5dd8 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -7,7 +7,7 @@ //! Messages come either from sibling parachains via XCM, or BridgeHub itself //! via the `snowbridge-pallet-system`: //! -//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver` +//! 1. `snowbridge_outbound_router_primitives::EthereumBlobExporter::deliver` //! 2. `snowbridge_pallet_system::Pallet::send` //! //! The message submission pipeline works like this: diff --git a/bridges/snowbridge/primitives/outbound-router/Cargo.toml b/bridges/snowbridge/primitives/outbound-router/Cargo.toml new file mode 100644 index 000000000000..17601d440973 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "snowbridge-outbound-router-primitives" +description = "Snowbridge Router Primitives" +version = "0.9.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +codec = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +log = { workspace = true } + +frame-support = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-builder = { workspace = true } + +snowbridge-core = { workspace = true } + +hex-literal = { workspace = true, default-features = true } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "log/std", + "scale-info/std", + "snowbridge-core/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/bridges/snowbridge/primitives/outbound-router/README.md b/bridges/snowbridge/primitives/outbound-router/README.md new file mode 100644 index 000000000000..0544d08e43c7 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/README.md @@ -0,0 +1,4 @@ +# Outbound Router Primitives + +Outbound router logic. Does XCM conversion to a lowered, simpler format the Ethereum contracts can +understand. diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/lib.rs similarity index 65% rename from bridges/snowbridge/primitives/router/src/outbound/mod.rs rename to bridges/snowbridge/primitives/outbound-router/src/lib.rs index 22756b222812..7ab04608543d 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/lib.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +#![cfg_attr(not(feature = "std"), no_std)] + pub mod v1; pub mod v2; diff --git a/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs new file mode 100644 index 000000000000..6394ba927d8a --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +#[cfg(test)] +mod tests; + +use core::slice::Iter; + +use codec::{Decode, Encode}; + +use frame_support::{ensure, traits::Get}; +use snowbridge_core::{ + outbound::v1::{AgentExecuteCommand, Command, Message, SendMessage}, + AgentId, ChannelId, ParaId, TokenId, TokenIdOf, +}; +use sp_core::{H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + )>, +); + +impl + ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + > +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().take().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, local_sub) = universal_source.clone() + .take() + .ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); + SendError::NotApplicable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let para_id = match local_sub.as_slice() { + [Parachain(para_id)] => *para_id, + _ => { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); + return Err(SendError::NotApplicable) + }, + }; + + let source_location = Location::new(1, local_sub.clone()); + + let agent_id = match AgentHashedDescription::convert_location(&source_location) { + Some(id) => id, + None => { + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); + return Err(SendError::NotApplicable) + }, + }; + + let message = message.take().ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); + SendError::MissingArgument + })?; + + let mut converter = + XcmConverter::::new(&message, expected_network, agent_id); + let (command, message_id) = converter.convert().map_err(|err|{ + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + let channel_id: ChannelId = ParaId::from(para_id).into(); + + let outbound_message = Message { id: Some(message_id.into()), channel_id, command }; + + // validate the message + let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + // convert fee to Asset + let fee = Asset::from((Location::parent(), fee.total())).into(); + + Ok(((ticket.encode(), message_id), fee)) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// Errors that can be thrown to the pattern matching step. +#[derive(PartialEq, Debug)] +enum XcmConverterError { + UnexpectedEndOfXcm, + EndOfXcmMessageExpected, + WithdrawAssetExpected, + DepositAssetExpected, + NoReserveAssets, + FilterDoesNotConsumeAllAssets, + TooManyAssets, + ZeroAssetTransfer, + BeneficiaryResolutionFailed, + AssetResolutionFailed, + InvalidFeeAsset, + SetTopicExpected, + ReserveAssetDepositedExpected, + InvalidAsset, + UnexpectedInstruction, +} + +macro_rules! match_expression { + ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => Some($value), + _ => None, + } + }; +} + +struct XcmConverter<'a, ConvertAssetId, Call> { + iter: Peekable>>, + ethereum_network: NetworkId, + agent_id: AgentId, + _marker: PhantomData, +} +impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> +where + ConvertAssetId: MaybeEquivalence, +{ + fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { + Self { + iter: message.inner().iter().peekable(), + ethereum_network, + agent_id, + _marker: Default::default(), + } + } + + fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { + let result = match self.peek() { + Ok(ReserveAssetDeposited { .. }) => self.make_mint_foreign_token_command(), + // Get withdraw/deposit and make native tokens create message. + Ok(WithdrawAsset { .. }) => self.make_unlock_native_token_command(), + Err(e) => Err(e), + _ => return Err(XcmConverterError::UnexpectedInstruction), + }?; + + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(XcmConverterError::EndOfXcmMessageExpected) + } + + Ok(result) + } + + fn make_unlock_native_token_command( + &mut self, + ) -> Result<(Command, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets from WithdrawAsset. + let reserve_assets = + match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) + .ok_or(WithdrawAssetExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // Fees are collected on AH, up front and directly from the user, to cover the + // complete cost of the transfer. Any additional fees provided in the XCM program are + // refunded to the beneficiary. We only validate the fee here if its provided to make sure + // the XCM program is well formed. Another way to think about this from an XCM perspective + // would be that the user offered to pay X amount in fees, but we charge 0 of that X amount + // (no fee) and refund X to the user. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (token, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // Check if there is a SetTopic and skip over it if found. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok(( + Command::AgentExecute { + agent_id: self.agent_id, + command: AgentExecuteCommand::TransferToken { token, recipient, amount }, + }, + *topic_id, + )) + } + + fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { + self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { + self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn network_matches(&self, network: &Option) -> bool { + if let Some(network) = network { + *network == self.ethereum_network + } else { + true + } + } + + /// Convert the xcm for Polkadot-native token from AH into the Command + /// To match transfers of Polkadot-native tokens, we expect an input of the form: + /// # ReserveAssetDeposited + /// # ClearOrigin + /// # BuyExecution + /// # DepositAsset + /// # SetTopic + fn make_mint_foreign_token_command( + &mut self, + ) -> Result<(Command, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets. + let reserve_assets = + match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) + .ok_or(ReserveAssetDepositedExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // Fees are collected on AH, up front and directly from the user, to cover the + // complete cost of the transfer. Any additional fees provided in the XCM program are + // refunded to the beneficiary. We only validate the fee here if its provided to make sure + // the XCM program is well formed. Another way to think about this from an XCM perspective + // would be that the user offered to pay X amount in fees, but we charge 0 of that X amount + // (no fee) and refund X to the user. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (asset_id, amount) = match reserve_asset { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + + ensure!(asset_id == expected_asset_id, InvalidAsset); + + // Check if there is a SetTopic and skip over it if found. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok((Command::MintForeignToken { token_id, recipient, amount }, *topic_id)) + } +} diff --git a/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs b/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs new file mode 100644 index 000000000000..607e2ea611a4 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs @@ -0,0 +1,1274 @@ +use frame_support::parameter_types; +use hex_literal::hex; +use snowbridge_core::{ + outbound::{v1::Fee, SendError, SendMessageFeeProvider}, + AgentIdOf, +}; +use sp_std::default::Default; +use xcm::{ + latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, + prelude::SendError as XcmSendError, +}; + +use super::*; + +parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; +} + +struct MockOkOutboundQueue; +impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Ok(((), Fee { local: 1, remote: 1 })) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} +struct MockErrOutboundQueue; +impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } +} + +impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +#[test] +fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some( + [OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild] + .into(), + ); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_without_global_universal_location_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Kusama), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee, weight_limit: Unlimited }, + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::Unroutable)); +} + +#[test] +fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + + let mut message: Option> = + Some(vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::Unroutable)); +} + +#[test] +fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert!(result.is_ok()); +} + +#[test] +fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) +} + +#[test] +fn xcm_converter_convert_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_without_buy_execution_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + ClearTopic, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); +} + +#[test] +fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_with_different_fee_asset_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = + Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); +} + +#[test] +fn xcm_converter_with_fees_greater_than_reserve_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); +} + +#[test] +fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let message: Xcm<()> = vec![].into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ClearError, + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); +} + +#[test] +fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee = Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); +} + +#[test] +fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { + let network = BridgedNetwork::get(); + + let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![ + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), + fun: Fungible(1000), + }, + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); +} + +#[test] +fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); +} + +#[test] +fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(0), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }].into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 32] = + hex!("2000000000000000000000000000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: [ + GlobalConsensus(Polkadot), + Parachain(1000), + AccountId32 { network: Some(Polkadot), id: beneficiary_address }, + ] + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed() +{ + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { + network: Some(Ethereum { chain_id: 2 }), + key: beneficiary_address, + } + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn test_describe_asset_hub() { + let legacy_location: Location = Location::new(0, [Parachain(1000)]); + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: Location = Location::new(1, [Parachain(1000)]); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) +} + +#[test] +fn test_describe_here() { + let location: Location = Location::new(0, []); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) +} + +#[test] +fn xcm_converter_transfer_native_token_success() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); + let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let expected_payload = + Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + // Invalid asset location from a different consensus + let asset_location = + Location { parents: 2, interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into() }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ReserveAssetDeposited(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); +} + +#[test] +fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} + +#[test] +fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs new file mode 100644 index 000000000000..8253322c34d5 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into InboundMessage that can be processed by the Gateway contract + +use codec::DecodeAll; +use core::slice::Iter; +use frame_support::{ensure, traits::Get, BoundedVec}; +use snowbridge_core::{ + outbound::{ + v2::{Command, Message}, + TransactInfo, + }, + TokenId, TokenIdOf, TokenIdOf as LocationIdOf, +}; +use sp_core::H160; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; +use xcm::prelude::*; +use xcm_executor::traits::ConvertLocation; + +/// Errors that can be thrown to the pattern matching step. +#[derive(PartialEq, Debug)] +pub enum XcmConverterError { + UnexpectedEndOfXcm, + EndOfXcmMessageExpected, + WithdrawAssetExpected, + DepositAssetExpected, + NoReserveAssets, + FilterDoesNotConsumeAllAssets, + TooManyAssets, + ZeroAssetTransfer, + BeneficiaryResolutionFailed, + AssetResolutionFailed, + InvalidFeeAsset, + SetTopicExpected, + ReserveAssetDepositedExpected, + InvalidAsset, + UnexpectedInstruction, + TooManyCommands, + AliasOriginExpected, + InvalidOrigin, + TransactDecodeFailed, + TransactParamsDecodeFailed, + FeeAssetResolutionFailed, + CallContractValueInsufficient, +} + +macro_rules! match_expression { + ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => Some($value), + _ => None, + } + }; +} + +pub struct XcmConverter<'a, ConvertAssetId, WETHAddress, Call> { + iter: Peekable>>, + ethereum_network: NetworkId, + _marker: PhantomData<(ConvertAssetId, WETHAddress)>, +} +impl<'a, ConvertAssetId, WETHAddress, Call> XcmConverter<'a, ConvertAssetId, WETHAddress, Call> +where + ConvertAssetId: MaybeEquivalence, + WETHAddress: Get, +{ + pub fn new(message: &'a Xcm, ethereum_network: NetworkId) -> Self { + Self { + iter: message.inner().iter().peekable(), + ethereum_network, + _marker: Default::default(), + } + } + + pub fn convert(&mut self) -> Result { + let result = self.to_ethereum_message()?; + Ok(result) + } + + fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { + self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { + self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn network_matches(&self, network: &Option) -> bool { + if let Some(network) = network { + *network == self.ethereum_network + } else { + true + } + } + + /// Extract the fee asset item from PayFees(V5) + fn extract_remote_fee(&mut self) -> Result { + use XcmConverterError::*; + let _ = match_expression!(self.next()?, WithdrawAsset(fee), fee) + .ok_or(WithdrawAssetExpected)?; + let fee_asset = + match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; + let (fee_asset_id, fee_amount) = match fee_asset { + Asset { id: asset_id, fun: Fungible(amount) } => Some((asset_id, *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + let weth_address = match_expression!( + fee_asset_id.0.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(FeeAssetResolutionFailed)?; + ensure!(weth_address == WETHAddress::get(), InvalidFeeAsset); + Ok(fee_amount) + } + + /// Convert the xcm for into the Message which will be executed + /// on Ethereum Gateway contract, we expect an input of the form: + /// # WithdrawAsset(WETH) + /// # PayFees(WETH) + /// # ReserveAssetDeposited(PNA) | WithdrawAsset(ENA) + /// # AliasOrigin(Origin) + /// # DepositAsset(PNA|ENA) + /// # Transact() ---Optional + /// # SetTopic + fn to_ethereum_message(&mut self) -> Result { + use XcmConverterError::*; + + // Get fee amount + let fee_amount = self.extract_remote_fee()?; + + // Get ENA reserve asset from WithdrawAsset. + let enas = + match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets); + if enas.is_some() { + let _ = self.next(); + } + + // Get PNA reserve asset from ReserveAssetDeposited + let pnas = match_expression!( + self.peek(), + Ok(ReserveAssetDeposited(reserve_assets)), + reserve_assets + ); + if pnas.is_some() { + let _ = self.next(); + } + // Check AliasOrigin. + let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin) + .ok_or(AliasOriginExpected)?; + let origin = LocationIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?; + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary.unpack(), + (0, [AccountKey20 { network, key }]) + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if enas.is_none() && pnas.is_none() { + return Err(NoReserveAssets) + } + + let mut commands: Vec = Vec::new(); + let mut weth_amount = 0; + + // ENA transfer commands + if let Some(enas) = enas { + for ena in enas.clone().inner().iter() { + // Check the the deposit asset filter matches what was reserved. + if !deposit_assets.matches(ena) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // only fungible asset is allowed + let (token, amount) = match ena { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + match inner_location.unpack() { + (0, [AccountKey20 { network, key }]) + if self.network_matches(network) => + Some((H160(*key), *amount)), + _ => None, + }, + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + if token == WETHAddress::get() { + weth_amount = amount; + } + + commands.push(Command::UnlockNativeToken { token, recipient, amount }); + } + } + + // PNA transfer commands + if let Some(pnas) = pnas { + ensure!(pnas.len() > 0, NoReserveAssets); + for pna in pnas.clone().inner().iter() { + // Check the the deposit asset filter matches what was reserved. + if !deposit_assets.matches(pna) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // Only fungible is allowed + let (asset_id, amount) = match pna { + Asset { id: AssetId(inner_location), fun: Fungible(amount) } => + Some((inner_location.clone(), *amount)), + _ => None, + } + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // Ensure PNA already registered + let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; + let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; + ensure!(asset_id == expected_asset_id, InvalidAsset); + + commands.push(Command::MintForeignToken { token_id, recipient, amount }); + } + } + + // Transact commands + let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call); + if let Some(transact_call) = transact_call { + let _ = self.next(); + let transact = + TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) + .map_err(|_| TransactDecodeFailed)?; + if transact.value > 0 { + ensure!(weth_amount > transact.value, CallContractValueInsufficient); + } + commands.push(Command::CallContract { + target: transact.target, + data: transact.data, + gas_limit: transact.gas_limit, + value: transact.value, + }); + } + + // ensure SetTopic exists + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + let message = Message { + id: (*topic_id).into(), + origin_location: origin_location.clone(), + origin, + fee: fee_amount, + commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?, + }; + + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(EndOfXcmMessageExpected) + } + + Ok(message) + } +} diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs new file mode 100644 index 000000000000..fe719e68ea04 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +#[cfg(test)] +mod tests; + +pub mod convert; +use convert::XcmConverter; + +use codec::{Decode, Encode}; +use frame_support::{ + ensure, + traits::{Contains, Get, ProcessMessageError}, +}; +use snowbridge_core::{outbound::v2::SendMessage, TokenId}; +use sp_core::{H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*}; +use xcm::prelude::*; +use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +pub const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + WETHAddress, +>( + PhantomData<( + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + WETHAddress, + )>, +); + +impl< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + WETHAddress, + > ExportXcm + for EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, + ConvertAssetId, + WETHAddress, + > +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, + ConvertAssetId: MaybeEquivalence, + WETHAddress: Get, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + log::debug!(target: TARGET, "message route through bridge {message:?}."); + + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, _) = universal_source.clone() + .ok_or_else(|| { + log::error!(target: TARGET, "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'."); + SendError::NotApplicable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let message = message.clone().ok_or_else(|| { + log::error!(target: TARGET, "xcm message not provided."); + SendError::MissingArgument + })?; + + // Inspect AliasOrigin as V2 message + let mut instructions = message.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + ensure!(result.is_err(), SendError::NotApplicable); + + let mut converter = + XcmConverter::::new(&message, expected_network); + let message = converter.convert().map_err(|err| { + log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + // validate the message + let (ticket, _) = OutboundQueue::validate(&message).map_err(|err| { + log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default())) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: TARGET, "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: TARGET, "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: TARGET, "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// An adapter for the implementation of `ExporterFor`, which attempts to find the +/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm` +/// in the provided `T` table containing various exporters. +pub struct XcmFilterExporter(core::marker::PhantomData<(T, M)>); +impl>> ExporterFor for XcmFilterExporter { + fn exporter_for( + network: &NetworkId, + remote_location: &InteriorLocation, + xcm: &Xcm<()>, + ) -> Option<(Location, Option)> { + // check the XCM + if !M::contains(xcm) { + return None + } + // check `network` and `remote_location` + T::exporter_for(network, remote_location, xcm) + } +} + +/// Xcm for SnowbridgeV2 which requires XCMV5 +pub struct XcmForSnowbridgeV2; +impl Contains> for XcmForSnowbridgeV2 { + fn contains(xcm: &Xcm<()>) -> bool { + let mut instructions = xcm.clone().0; + let result = instructions.matcher().match_next_inst_while( + |_| true, + |inst| { + return match inst { + AliasOrigin(..) => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + } + }, + ); + result.is_err() + } +} diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs new file mode 100644 index 000000000000..835c7abc59aa --- /dev/null +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs @@ -0,0 +1,1288 @@ +use super::*; +use crate::v2::convert::XcmConverterError; +use frame_support::{parameter_types, BoundedVec}; +use hex_literal::hex; +use snowbridge_core::{ + outbound::{ + v2::{Command, Message}, + SendError, SendMessageFeeProvider, + }, + AgentIdOf, TokenIdOf, +}; +use sp_std::default::Default; +use xcm::{latest::WESTEND_GENESIS_HASH, prelude::SendError as XcmSendError}; + +parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); + pub const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + pub const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; + pub WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); +} + +struct MockOkOutboundQueue; +impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Ok(((), 1_u128)) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} +struct MockErrOutboundQueue; +impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } +} + +impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +#[test] +fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some( + [OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild] + .into(), + ); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_without_global_universal_location_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Kusama), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, + ExpectAsset(fee.into()), + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let channel: u32 = 0; + let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; + let fees: Assets = vec![fee.clone()].into(); + + let mut message: Option> = + Some(vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + let filter: AssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate(network, channel, &mut universal_source, &mut destination, &mut message); + + assert!(result.is_ok()); +} + +#[test] +fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) +} + +#[test] +fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} + +#[test] +fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + WETHAddress, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} + +#[test] +fn xcm_converter_convert_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert!(result.is_ok()); +} + +#[test] +fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(All); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.is_ok(), true); +} + +#[test] +fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + ClearTopic, + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); +} + +#[test] +fn xcm_converter_convert_with_partial_message_yields_invalid_fee_asset() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); + + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_with_different_fee_asset_succeed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = [AccountKey20 { network: None, key: token_address }].into(); + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.is_ok(), true); +} + +#[test] +fn xcm_converter_with_fees_greater_than_reserve_succeed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); + + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.is_ok(), true); +} + +#[test] +fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let message: Xcm<()> = vec![].into(); + + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ClearError, + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); +} + +#[test] +fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); +} + +#[test] +fn xcm_converter_convert_with_two_assets_yields() { + let network = BridgedNetwork::get(); + + let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![ + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), + fun: Fungible(1000), + }, + Asset { + id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.is_ok(), true); +} + +#[test] +fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); +} + +#[test] +fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(0), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), + fun: Fungible(1000), + } + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId( + [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }].into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 32] = + hex!("2000000000000000000000000000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountId32 { network: Some(Polkadot), id: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed() +{ + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: Assets = vec![Asset { + id: AssetId(AccountKey20 { network: None, key: token_address }.into()), + fun: Fungible(1000), + }] + .into(); + let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + WithdrawAsset(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { + network: Some(Ethereum { chain_id: 2 }), + key: beneficiary_address, + } + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn test_describe_asset_hub() { + let legacy_location: Location = Location::new(0, [Parachain(1000)]); + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: Location = Location::new(1, [Parachain(1000)]); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) +} + +#[test] +fn test_describe_here() { + let location: Location = Location::new(0, []); + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) +} + +#[test] +fn xcm_converter_transfer_native_token_success() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); + let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); + + let assets: Assets = + vec![Asset { id: AssetId(asset_location.clone()), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + ReserveAssetDeposited(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let expected_payload = + Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; + let expected_message = Message { + origin_location: Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)]), + id: [0; 32].into(), + origin: hex!("aa16eddac8725928eaeda4aae518bf10d02bee80382517d21464a5cdf8d1d8e1").into(), + fee: 1000, + commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), + }; + let result = converter.convert(); + assert_eq!(result, Ok(expected_message)); +} + +#[test] +fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let amount = 1000000; + // Invalid asset location from a different consensus + let asset_location = Location { + parents: 2, + interior: [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))].into(), + }; + + let assets: Assets = vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); + let filter: AssetFilter = assets.clone().into(); + + let fee_asset: Asset = Asset { + id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + PayFees { asset: fee_asset }, + ReserveAssetDeposited(assets.clone()), + AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::::new(&message, network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); +} diff --git a/bridges/snowbridge/primitives/router/src/lib.rs b/bridges/snowbridge/primitives/router/src/lib.rs index d9031c69b22b..d745687c496b 100644 --- a/bridges/snowbridge/primitives/router/src/lib.rs +++ b/bridges/snowbridge/primitives/router/src/lib.rs @@ -3,4 +3,3 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod inbound; -pub mod outbound; diff --git a/bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs deleted file mode 100644 index f952d5c613f9..000000000000 --- a/bridges/snowbridge/primitives/router/src/outbound/v1/mod.rs +++ /dev/null @@ -1,1703 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Converts XCM messages into simpler commands that can be processed by the Gateway contract - -use core::slice::Iter; - -use codec::{Decode, Encode}; - -use frame_support::{ensure, traits::Get}; -use snowbridge_core::{ - outbound::v1::{AgentExecuteCommand, Command, Message, SendMessage}, - AgentId, ChannelId, ParaId, TokenId, TokenIdOf, -}; -use sp_core::{H160, H256}; -use sp_runtime::traits::MaybeEquivalence; -use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; -use xcm::prelude::*; -use xcm_executor::traits::{ConvertLocation, ExportXcm}; - -pub struct EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, ->( - PhantomData<( - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - )>, -); - -impl - ExportXcm - for EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - > -where - UniversalLocation: Get, - EthereumNetwork: Get, - OutboundQueue: SendMessage, - AgentHashedDescription: ConvertLocation, - ConvertAssetId: MaybeEquivalence, -{ - type Ticket = (Vec, XcmHash); - - fn validate( - network: NetworkId, - _channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - let expected_network = EthereumNetwork::get(); - let universal_location = UniversalLocation::get(); - - if network != expected_network { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning destination to avoid modifying the value so subsequent exporters can use it. - let dest = destination.clone().take().ok_or(SendError::MissingArgument)?; - if dest != Here { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, local_sub) = universal_source.clone() - .take() - .ok_or_else(|| { - log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); - SendError::MissingArgument - })? - .split_global() - .map_err(|()| { - log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); - SendError::NotApplicable - })?; - - if Ok(local_net) != universal_location.global_consensus() { - log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}."); - return Err(SendError::NotApplicable) - } - - let para_id = match local_sub.as_slice() { - [Parachain(para_id)] => *para_id, - _ => { - log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); - return Err(SendError::NotApplicable) - }, - }; - - let source_location = Location::new(1, local_sub.clone()); - - let agent_id = match AgentHashedDescription::convert_location(&source_location) { - Some(id) => id, - None => { - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); - return Err(SendError::NotApplicable) - }, - }; - - let message = message.clone().ok_or_else(|| { - log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); - SendError::MissingArgument - })?; - - let mut converter = - XcmConverter::::new(&message, expected_network, agent_id); - let (command, message_id) = converter.convert().map_err(|err|{ - log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); - SendError::Unroutable - })?; - - let channel_id: ChannelId = ParaId::from(para_id).into(); - - let outbound_message = Message { id: Some(message_id.into()), channel_id, command }; - - // validate the message - let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { - log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); - SendError::Unroutable - })?; - - // convert fee to Asset - let fee = Asset::from((Location::parent(), fee.total())).into(); - - Ok(((ticket.encode(), message_id), fee)) - } - - fn deliver(blob: (Vec, XcmHash)) -> Result { - let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) - .map_err(|_| { - log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error"); - SendError::NotApplicable - })?; - - let message_id = OutboundQueue::deliver(ticket).map_err(|_| { - log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed"); - SendError::Transport("other transport error") - })?; - - log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}."); - Ok(message_id.into()) - } -} - -/// Errors that can be thrown to the pattern matching step. -#[derive(PartialEq, Debug)] -enum XcmConverterError { - UnexpectedEndOfXcm, - EndOfXcmMessageExpected, - WithdrawAssetExpected, - DepositAssetExpected, - NoReserveAssets, - FilterDoesNotConsumeAllAssets, - TooManyAssets, - ZeroAssetTransfer, - BeneficiaryResolutionFailed, - AssetResolutionFailed, - InvalidFeeAsset, - SetTopicExpected, - ReserveAssetDepositedExpected, - InvalidAsset, - UnexpectedInstruction, -} - -macro_rules! match_expression { - ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { - match $expression { - $( $pattern )|+ $( if $guard )? => Some($value), - _ => None, - } - }; -} - -struct XcmConverter<'a, ConvertAssetId, Call> { - iter: Peekable>>, - ethereum_network: NetworkId, - agent_id: AgentId, - _marker: PhantomData, -} -impl<'a, ConvertAssetId, Call> XcmConverter<'a, ConvertAssetId, Call> -where - ConvertAssetId: MaybeEquivalence, -{ - fn new(message: &'a Xcm, ethereum_network: NetworkId, agent_id: AgentId) -> Self { - Self { - iter: message.inner().iter().peekable(), - ethereum_network, - agent_id, - _marker: Default::default(), - } - } - - fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { - let result = match self.peek() { - Ok(ReserveAssetDeposited { .. }) => self.send_native_tokens_message(), - // Get withdraw/deposit and make native tokens create message. - Ok(WithdrawAsset { .. }) => self.send_tokens_message(), - Err(e) => Err(e), - _ => return Err(XcmConverterError::UnexpectedInstruction), - }?; - - // All xcm instructions must be consumed before exit. - if self.next().is_ok() { - return Err(XcmConverterError::EndOfXcmMessageExpected) - } - - Ok(result) - } - - fn send_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { - use XcmConverterError::*; - - // Get the reserve assets from WithdrawAsset. - let reserve_assets = - match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) - .ok_or(WithdrawAssetExpected)?; - - // Check if clear origin exists and skip over it. - if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { - let _ = self.next(); - } - - // Get the fee asset item from BuyExecution or continue parsing. - let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); - if fee_asset.is_some() { - let _ = self.next(); - } - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if reserve_assets.len() == 0 { - return Err(NoReserveAssets) - } - - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - - // If there was a fee specified verify it. - if let Some(fee_asset) = fee_asset { - // The fee asset must be the same as the reserve asset. - if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { - return Err(InvalidFeeAsset) - } - } - - let (token, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - match inner_location.unpack() { - (0, [AccountKey20 { network, key }]) if self.network_matches(network) => - Some((H160(*key), *amount)), - _ => None, - }, - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - // Check if there is a SetTopic. - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - Ok(( - Command::AgentExecute { - agent_id: self.agent_id, - command: AgentExecuteCommand::TransferToken { token, recipient, amount }, - }, - *topic_id, - )) - } - - fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { - self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { - self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn network_matches(&self, network: &Option) -> bool { - if let Some(network) = network { - *network == self.ethereum_network - } else { - true - } - } - - /// Convert the xcm for Polkadot-native token from AH into the Command - /// To match transfers of Polkadot-native tokens, we expect an input of the form: - /// # ReserveAssetDeposited - /// # ClearOrigin - /// # BuyExecution - /// # DepositAsset - /// # SetTopic - fn send_native_tokens_message(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> { - use XcmConverterError::*; - - // Get the reserve assets. - let reserve_assets = - match_expression!(self.next()?, ReserveAssetDeposited(reserve_assets), reserve_assets) - .ok_or(ReserveAssetDepositedExpected)?; - - // Check if clear origin exists and skip over it. - if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { - let _ = self.next(); - } - - // Get the fee asset item from BuyExecution or continue parsing. - let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); - if fee_asset.is_some() { - let _ = self.next(); - } - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if reserve_assets.len() == 0 { - return Err(NoReserveAssets) - } - - // Check the the deposit asset filter matches what was reserved. - if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // We only support a single asset at a time. - ensure!(reserve_assets.len() == 1, TooManyAssets); - let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; - - // If there was a fee specified verify it. - if let Some(fee_asset) = fee_asset { - // The fee asset must be the same as the reserve asset. - if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { - return Err(InvalidFeeAsset) - } - } - - let (asset_id, amount) = match reserve_asset { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - Some((inner_location.clone(), *amount)), - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; - - let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; - - ensure!(asset_id == expected_asset_id, InvalidAsset); - - // Check if there is a SetTopic. - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - Ok((Command::MintForeignToken { token_id, recipient, amount }, *topic_id)) - } -} - -#[cfg(test)] -mod tests { - use frame_support::parameter_types; - use hex_literal::hex; - use snowbridge_core::{ - outbound::{v1::Fee, SendError, SendMessageFeeProvider}, - AgentIdOf, - }; - use sp_std::default::Default; - use xcm::{ - latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, - prelude::SendError as XcmSendError, - }; - - use super::*; - - parameter_types! { - const MaxMessageSize: u32 = u32::MAX; - const RelayNetwork: NetworkId = Polkadot; - UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); - const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; - const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; - } - - struct MockOkOutboundQueue; - impl SendMessage for MockOkOutboundQueue { - type Ticket = (); - - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Ok(((), Fee { local: 1, remote: 1 })) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } - } - - impl SendMessageFeeProvider for MockOkOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - struct MockErrOutboundQueue; - impl SendMessage for MockErrOutboundQueue { - type Ticket = (); - - fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { - Err(SendError::MessageTooLarge) - } - - fn deliver(_: Self::Ticket) -> Result { - Err(SendError::MessageTooLarge) - } - } - - impl SendMessageFeeProvider for MockErrOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - - pub struct MockTokenIdConvert; - impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) - } - fn convert_back(_loc: &Location) -> Option { - None - } - } - - #[test] - fn exporter_validate_with_unknown_network_yields_not_applicable() { - let network = Ethereum { chain_id: 1337 }; - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_invalid_destination_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_x8_destination_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Some( - [ - OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, - OnlyChild, - ] - .into(), - ); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_universal_source_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_without_global_universal_location_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_global_bridge_location_yields_not_applicable() { - let network = NonBridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_remote_universal_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Kusama), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_xcm_message_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_max_target_fee_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(fees), - BuyExecution { fees: fee, weight_limit: Unlimited }, - WithdrawAsset(assets), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } - .into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::Unroutable)); - } - - #[test] - fn exporter_validate_with_unparsable_xcm_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - - let mut message: Option> = Some( - vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::Unroutable)); - } - - #[test] - fn exporter_validate_xcm_success_case_1() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert!(result.is_ok()); - } - - #[test] - fn exporter_deliver_with_submit_failure_yields_unroutable() { - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockErrOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); - assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) - } - - #[test] - fn xcm_converter_convert_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); - } - - #[test] - fn xcm_converter_convert_without_buy_execution_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); - } - - #[test] - fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(All); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); - } - - #[test] - fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(500) }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = Command::AgentExecute { - agent_id: Default::default(), - command: AgentExecuteCommand::TransferToken { - token: token_address.into(), - recipient: beneficiary_address.into(), - amount: 1000, - }, - }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); - } - - #[test] - fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - ClearTopic, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); - } - - #[test] - fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); - - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); - } - - #[test] - fn xcm_converter_with_different_fee_asset_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = - Asset { id: AssetId(Location { parents: 0, interior: Here }), fun: Fungible(1000) }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); - } - - #[test] - fn xcm_converter_with_fees_greater_than_reserve_fails() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset = Asset { id: AssetId(asset_location.clone()), fun: Fungible(1001) }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee_asset, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); - } - - #[test] - fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { - let network = BridgedNetwork::get(); - - let message: Xcm<()> = vec![].into(); - - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); - } - - #[test] - fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ClearError, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); - } - - #[test] - fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedInstruction)); - } - - #[test] - fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); - } - - #[test] - fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![].into(); - let filter: AssetFilter = assets.clone().into(); - - let fee = Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); - } - - #[test] - fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { - let network = BridgedNetwork::get(); - - let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![ - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), - fun: Fungible(1000), - }, - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), - fun: Fungible(500), - }, - ] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); - } - - #[test] - fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); - } - - #[test] - fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(0), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }] - .into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_address: [u8; 32] = - hex!("2000000000000000000000000000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: [ - GlobalConsensus(Polkadot), - Parachain(1000), - AccountId32 { network: Some(Polkadot), id: beneficiary_address }, - ] - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed( - ) { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { - network: Some(Ethereum { chain_id: 2 }), - key: beneficiary_address, - } - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); - } - - #[test] - fn test_describe_asset_hub() { - let legacy_location: Location = Location::new(0, [Parachain(1000)]); - let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); - assert_eq!( - legacy_agent_id, - hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() - ); - let location: Location = Location::new(1, [Parachain(1000)]); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() - ) - } - - #[test] - fn test_describe_here() { - let location: Location = Location::new(0, []); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() - ) - } - - #[test] - fn xcm_converter_transfer_native_token_success() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); - let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let expected_payload = - Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; - let result = converter.convert(); - assert_eq!(result, Ok((expected_payload, [0; 32]))); - } - - #[test] - fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - // Invalid asset location from a different consensus - let asset_location = Location { - parents: 2, - interior: [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))].into(), - }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ReserveAssetDeposited(assets.clone()), - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network, Default::default()); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); - } - - #[test] - fn exporter_validate_with_invalid_dest_does_not_alter_destination() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Parachain(1000).into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(Polkadot), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } - - #[test] - fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Here.into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } -} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs deleted file mode 100644 index 77616bde2796..000000000000 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/convert.rs +++ /dev/null @@ -1,1068 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Converts XCM messages into InboundMessage that can be processed by the Gateway contract - -use codec::DecodeAll; -use core::slice::Iter; -use frame_support::{ensure, traits::Get, BoundedVec}; -use snowbridge_core::{ - outbound::{ - v2::{Command, Message}, - TransactInfo, - }, - TokenId, TokenIdOf, TokenIdOf as LocationIdOf, -}; -use sp_core::H160; -use sp_runtime::traits::MaybeEquivalence; -use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; -use xcm::prelude::*; -use xcm_executor::traits::ConvertLocation; - -/// Errors that can be thrown to the pattern matching step. -#[derive(PartialEq, Debug)] -pub enum XcmConverterError { - UnexpectedEndOfXcm, - EndOfXcmMessageExpected, - WithdrawAssetExpected, - DepositAssetExpected, - NoReserveAssets, - FilterDoesNotConsumeAllAssets, - TooManyAssets, - ZeroAssetTransfer, - BeneficiaryResolutionFailed, - AssetResolutionFailed, - InvalidFeeAsset, - SetTopicExpected, - ReserveAssetDepositedExpected, - InvalidAsset, - UnexpectedInstruction, - TooManyCommands, - AliasOriginExpected, - InvalidOrigin, - TransactDecodeFailed, - TransactParamsDecodeFailed, - FeeAssetResolutionFailed, - CallContractValueInsufficient, -} - -macro_rules! match_expression { - ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { - match $expression { - $( $pattern )|+ $( if $guard )? => Some($value), - _ => None, - } - }; -} - -pub struct XcmConverter<'a, ConvertAssetId, WETHAddress, Call> { - iter: Peekable>>, - ethereum_network: NetworkId, - _marker: PhantomData<(ConvertAssetId, WETHAddress)>, -} -impl<'a, ConvertAssetId, WETHAddress, Call> XcmConverter<'a, ConvertAssetId, WETHAddress, Call> -where - ConvertAssetId: MaybeEquivalence, - WETHAddress: Get, -{ - pub fn new(message: &'a Xcm, ethereum_network: NetworkId) -> Self { - Self { - iter: message.inner().iter().peekable(), - ethereum_network, - _marker: Default::default(), - } - } - - pub fn convert(&mut self) -> Result { - let result = self.to_ethereum_message()?; - Ok(result) - } - - fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { - self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { - self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) - } - - fn network_matches(&self, network: &Option) -> bool { - if let Some(network) = network { - *network == self.ethereum_network - } else { - true - } - } - - /// Extract the fee asset item from PayFees(V5) - fn extract_remote_fee(&mut self) -> Result { - use XcmConverterError::*; - let _ = match_expression!(self.next()?, WithdrawAsset(fee), fee) - .ok_or(WithdrawAssetExpected)?; - let fee_asset = - match_expression!(self.next()?, PayFees { asset: fee }, fee).ok_or(InvalidFeeAsset)?; - let (fee_asset_id, fee_amount) = match fee_asset { - Asset { id: asset_id, fun: Fungible(amount) } => Some((asset_id, *amount)), - _ => None, - } - .ok_or(AssetResolutionFailed)?; - let weth_address = match_expression!( - fee_asset_id.0.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(FeeAssetResolutionFailed)?; - ensure!(weth_address == WETHAddress::get(), InvalidFeeAsset); - Ok(fee_amount) - } - - /// Convert the xcm for into the Message which will be executed - /// on Ethereum Gateway contract, we expect an input of the form: - /// # WithdrawAsset(WETH) - /// # PayFees(WETH) - /// # ReserveAssetDeposited(PNA) | WithdrawAsset(ENA) - /// # AliasOrigin(Origin) - /// # DepositAsset(PNA|ENA) - /// # Transact() ---Optional - /// # SetTopic - fn to_ethereum_message(&mut self) -> Result { - use XcmConverterError::*; - - // Get fee amount - let fee_amount = self.extract_remote_fee()?; - - // Get ENA reserve asset from WithdrawAsset. - let enas = - match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets); - if enas.is_some() { - let _ = self.next(); - } - - // Get PNA reserve asset from ReserveAssetDeposited - let pnas = match_expression!( - self.peek(), - Ok(ReserveAssetDeposited(reserve_assets)), - reserve_assets - ); - if pnas.is_some() { - let _ = self.next(); - } - // Check AliasOrigin. - let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin) - .ok_or(AliasOriginExpected)?; - let origin = LocationIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?; - - let (deposit_assets, beneficiary) = match_expression!( - self.next()?, - DepositAsset { assets, beneficiary }, - (assets, beneficiary) - ) - .ok_or(DepositAssetExpected)?; - - // assert that the beneficiary is AccountKey20. - let recipient = match_expression!( - beneficiary.unpack(), - (0, [AccountKey20 { network, key }]) - if self.network_matches(network), - H160(*key) - ) - .ok_or(BeneficiaryResolutionFailed)?; - - // Make sure there are reserved assets. - if enas.is_none() && pnas.is_none() { - return Err(NoReserveAssets) - } - - let mut commands: Vec = Vec::new(); - let mut weth_amount = 0; - - // ENA transfer commands - if let Some(enas) = enas { - for ena in enas.clone().inner().iter() { - // Check the the deposit asset filter matches what was reserved. - if !deposit_assets.matches(ena) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // only fungible asset is allowed - let (token, amount) = match ena { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - match inner_location.unpack() { - (0, [AccountKey20 { network, key }]) - if self.network_matches(network) => - Some((H160(*key), *amount)), - _ => None, - }, - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - if token == WETHAddress::get() { - weth_amount = amount; - } - - commands.push(Command::UnlockNativeToken { token, recipient, amount }); - } - } - - // PNA transfer commands - if let Some(pnas) = pnas { - ensure!(pnas.len() > 0, NoReserveAssets); - for pna in pnas.clone().inner().iter() { - // Check the the deposit asset filter matches what was reserved. - if !deposit_assets.matches(pna) { - return Err(FilterDoesNotConsumeAllAssets) - } - - // Only fungible is allowed - let (asset_id, amount) = match pna { - Asset { id: AssetId(inner_location), fun: Fungible(amount) } => - Some((inner_location.clone(), *amount)), - _ => None, - } - .ok_or(AssetResolutionFailed)?; - - // transfer amount must be greater than 0. - ensure!(amount > 0, ZeroAssetTransfer); - - // Ensure PNA already registered - let token_id = TokenIdOf::convert_location(&asset_id).ok_or(InvalidAsset)?; - let expected_asset_id = ConvertAssetId::convert(&token_id).ok_or(InvalidAsset)?; - ensure!(asset_id == expected_asset_id, InvalidAsset); - - commands.push(Command::MintForeignToken { token_id, recipient, amount }); - } - } - - // Transact commands - let transact_call = match_expression!(self.peek(), Ok(Transact { call, .. }), call); - if let Some(transact_call) = transact_call { - let _ = self.next(); - let transact = - TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) - .map_err(|_| TransactDecodeFailed)?; - if transact.value > 0 { - ensure!(weth_amount > transact.value, CallContractValueInsufficient); - } - commands.push(Command::CallContract { - target: transact.target, - data: transact.data, - gas_limit: transact.gas_limit, - value: transact.value, - }); - } - - // ensure SetTopic exists - let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; - - let message = Message { - id: (*topic_id).into(), - origin_location: origin_location.clone(), - origin, - fee: fee_amount, - commands: BoundedVec::try_from(commands).map_err(|_| TooManyCommands)?, - }; - - // All xcm instructions must be consumed before exit. - if self.next().is_ok() { - return Err(EndOfXcmMessageExpected) - } - - Ok(message) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::outbound::v2::tests::{ - BridgedNetwork, MockTokenIdConvert, NonBridgedNetwork, WETHAddress, - }; - use hex_literal::hex; - use snowbridge_core::AgentIdOf; - use xcm::latest::WESTEND_GENESIS_HASH; - - #[test] - fn xcm_converter_convert_success() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert!(result.is_ok()); - } - - #[test] - fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(All); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.is_ok(), true); - } - - #[test] - fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - ClearTopic, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); - } - - #[test] - fn xcm_converter_convert_with_partial_message_yields_invalid_fee_asset() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); - - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); - } - - #[test] - fn xcm_converter_with_different_fee_asset_succeed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location = [AccountKey20 { network: None, key: token_address }].into(); - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.is_ok(), true); - } - - #[test] - fn xcm_converter_with_fees_greater_than_reserve_succeed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let asset_location: Location = [AccountKey20 { network: None, key: token_address }].into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(1000) }].into(); - - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.is_ok(), true); - } - - #[test] - fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { - let network = BridgedNetwork::get(); - - let message: Xcm<()> = vec![].into(); - - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); - } - - #[test] - fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ClearError, - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); - } - - #[test] - fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let message: Xcm<()> = vec![ - ClearOrigin, - BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); - } - - #[test] - fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); - } - - #[test] - fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![].into(); - let filter: AssetFilter = assets.clone().into(); - - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); - } - - #[test] - fn xcm_converter_convert_with_two_assets_yields() { - let network = BridgedNetwork::get(); - - let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![ - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_1 }.into()), - fun: Fungible(1000), - }, - Asset { - id: AssetId(AccountKey20 { network: None, key: token_address_2 }.into()), - fun: Fungible(500), - }, - ] - .into(); - let filter: AssetFilter = assets.clone().into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.is_ok(), true); - } - - #[test] - fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(0)); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); - } - - #[test] - fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(0), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId([GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)].into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone().into()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }.into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone().into()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId( - [AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }] - .into(), - ), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone().into()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - - let beneficiary_address: [u8; 32] = - hex!("2000000000000000000000000000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone().into()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountId32 { network: Some(Polkadot), id: beneficiary_address } - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); - } - - #[test] - fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed( - ) { - let network = BridgedNetwork::get(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = Wild(WildAsset::AllCounted(1)); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { - network: Some(Ethereum { chain_id: 2 }), - key: beneficiary_address, - } - .into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); - } - - #[test] - fn test_describe_asset_hub() { - let legacy_location: Location = Location::new(0, [Parachain(1000)]); - let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); - assert_eq!( - legacy_agent_id, - hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() - ); - let location: Location = Location::new(1, [Parachain(1000)]); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() - ) - } - - #[test] - fn test_describe_here() { - let location: Location = Location::new(0, []); - let agent_id = AgentIdOf::convert_location(&location).unwrap(); - assert_eq!( - agent_id, - hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() - ) - } - - #[test] - fn xcm_converter_transfer_native_token_success() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - let asset_location = Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]); - let token_id = TokenIdOf::convert_location(&asset_location).unwrap(); - - let assets: Assets = - vec![Asset { id: AssetId(asset_location.clone()), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - ReserveAssetDeposited(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let expected_payload = - Command::MintForeignToken { recipient: beneficiary_address.into(), amount, token_id }; - let expected_message = Message { - origin_location: Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)]), - id: [0; 32].into(), - origin: hex!("aa16eddac8725928eaeda4aae518bf10d02bee80382517d21464a5cdf8d1d8e1").into(), - fee: 1000, - commands: BoundedVec::try_from(vec![expected_payload]).unwrap(), - }; - let result = converter.convert(); - assert_eq!(result, Ok(expected_message)); - } - - #[test] - fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { - let network = BridgedNetwork::get(); - - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let amount = 1000000; - // Invalid asset location from a different consensus - let asset_location = Location { - parents: 2, - interior: [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))].into(), - }; - - let assets: Assets = - vec![Asset { id: AssetId(asset_location), fun: Fungible(amount) }].into(); - let filter: AssetFilter = assets.clone().into(); - - let fee_asset: Asset = Asset { - id: AssetId(AccountKey20 { network: None, key: WETHAddress::get().0 }.into()), - fun: Fungible(1000), - }; - - let message: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - ReserveAssetDeposited(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut converter = - XcmConverter::::new(&message, network); - let result = converter.convert(); - assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); - } -} diff --git a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs deleted file mode 100644 index 0fbfc2784efa..000000000000 --- a/bridges/snowbridge/primitives/router/src/outbound/v2/mod.rs +++ /dev/null @@ -1,738 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Converts XCM messages into simpler commands that can be processed by the Gateway contract - -pub mod convert; -use convert::XcmConverter; - -use codec::{Decode, Encode}; -use frame_support::{ - ensure, - traits::{Contains, Get, ProcessMessageError}, -}; -use snowbridge_core::{outbound::v2::SendMessage, TokenId}; -use sp_core::{H160, H256}; -use sp_runtime::traits::MaybeEquivalence; -use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*}; -use xcm::prelude::*; -use xcm_builder::{CreateMatcher, ExporterFor, MatchXcm}; -use xcm_executor::traits::{ConvertLocation, ExportXcm}; - -pub const TARGET: &'static str = "xcm::ethereum_blob_exporter::v2"; - -pub struct EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - WETHAddress, ->( - PhantomData<( - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - WETHAddress, - )>, -); - -impl< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - WETHAddress, - > ExportXcm - for EthereumBlobExporter< - UniversalLocation, - EthereumNetwork, - OutboundQueue, - AgentHashedDescription, - ConvertAssetId, - WETHAddress, - > -where - UniversalLocation: Get, - EthereumNetwork: Get, - OutboundQueue: SendMessage, - AgentHashedDescription: ConvertLocation, - ConvertAssetId: MaybeEquivalence, - WETHAddress: Get, -{ - type Ticket = (Vec, XcmHash); - - fn validate( - network: NetworkId, - _channel: u32, - universal_source: &mut Option, - destination: &mut Option, - message: &mut Option>, - ) -> SendResult { - log::debug!(target: TARGET, "message route through bridge {message:?}."); - - let expected_network = EthereumNetwork::get(); - let universal_location = UniversalLocation::get(); - - if network != expected_network { - log::trace!(target: TARGET, "skipped due to unmatched bridge network {network:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning destination to avoid modifying the value so subsequent exporters can use it. - let dest = destination.clone().ok_or(SendError::MissingArgument)?; - if dest != Here { - log::trace!(target: TARGET, "skipped due to unmatched remote destination {dest:?}."); - return Err(SendError::NotApplicable) - } - - // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, _) = universal_source.clone() - .ok_or_else(|| { - log::error!(target: TARGET, "universal source not provided."); - SendError::MissingArgument - })? - .split_global() - .map_err(|()| { - log::error!(target: TARGET, "could not get global consensus from universal source '{universal_source:?}'."); - SendError::NotApplicable - })?; - - if Ok(local_net) != universal_location.global_consensus() { - log::trace!(target: TARGET, "skipped due to unmatched relay network {local_net:?}."); - return Err(SendError::NotApplicable) - } - - let message = message.clone().ok_or_else(|| { - log::error!(target: TARGET, "xcm message not provided."); - SendError::MissingArgument - })?; - - // Inspect AliasOrigin as V2 message - let mut instructions = message.clone().0; - let result = instructions.matcher().match_next_inst_while( - |_| true, - |inst| { - return match inst { - AliasOrigin(..) => Err(ProcessMessageError::Yield), - _ => Ok(ControlFlow::Continue(())), - } - }, - ); - ensure!(result.is_err(), SendError::NotApplicable); - - let mut converter = - XcmConverter::::new(&message, expected_network); - let message = converter.convert().map_err(|err| { - log::error!(target: TARGET, "unroutable due to pattern matching error '{err:?}'."); - SendError::Unroutable - })?; - - // validate the message - let (ticket, _) = OutboundQueue::validate(&message).map_err(|err| { - log::error!(target: TARGET, "OutboundQueue validation of message failed. {err:?}"); - SendError::Unroutable - })?; - - Ok(((ticket.encode(), XcmHash::from(message.id)), Assets::default())) - } - - fn deliver(blob: (Vec, XcmHash)) -> Result { - let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) - .map_err(|_| { - log::trace!(target: TARGET, "undeliverable due to decoding error"); - SendError::NotApplicable - })?; - - let message_id = OutboundQueue::deliver(ticket).map_err(|_| { - log::error!(target: TARGET, "OutboundQueue submit of message failed"); - SendError::Transport("other transport error") - })?; - - log::info!(target: TARGET, "message delivered {message_id:#?}."); - Ok(message_id.into()) - } -} - -/// An adapter for the implementation of `ExporterFor`, which attempts to find the -/// `(bridge_location, payment)` for the requested `network` and `remote_location` and `xcm` -/// in the provided `T` table containing various exporters. -pub struct XcmFilterExporter(core::marker::PhantomData<(T, M)>); -impl>> ExporterFor for XcmFilterExporter { - fn exporter_for( - network: &NetworkId, - remote_location: &InteriorLocation, - xcm: &Xcm<()>, - ) -> Option<(Location, Option)> { - // check the XCM - if !M::contains(xcm) { - return None - } - // check `network` and `remote_location` - T::exporter_for(network, remote_location, xcm) - } -} - -/// Xcm for SnowbridgeV2 which requires XCMV5 -pub struct XcmForSnowbridgeV2; -impl Contains> for XcmForSnowbridgeV2 { - fn contains(xcm: &Xcm<()>) -> bool { - let mut instructions = xcm.clone().0; - let result = instructions.matcher().match_next_inst_while( - |_| true, - |inst| { - return match inst { - AliasOrigin(..) => Err(ProcessMessageError::Yield), - _ => Ok(ControlFlow::Continue(())), - } - }, - ); - result.is_err() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use frame_support::parameter_types; - use hex_literal::hex; - use snowbridge_core::{ - outbound::{v2::Message, SendError, SendMessageFeeProvider}, - AgentIdOf, - }; - use sp_std::default::Default; - use xcm::{latest::WESTEND_GENESIS_HASH, prelude::SendError as XcmSendError}; - - parameter_types! { - const MaxMessageSize: u32 = u32::MAX; - const RelayNetwork: NetworkId = Polkadot; - UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(1013)].into(); - pub const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; - pub const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; - pub WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); - } - - struct MockOkOutboundQueue; - impl SendMessage for MockOkOutboundQueue { - type Ticket = (); - - type Balance = u128; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { - Ok(((), 1_u128)) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } - } - - impl SendMessageFeeProvider for MockOkOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - struct MockErrOutboundQueue; - impl SendMessage for MockErrOutboundQueue { - type Ticket = (); - - type Balance = u128; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { - Err(SendError::MessageTooLarge) - } - - fn deliver(_: Self::Ticket) -> Result { - Err(SendError::MessageTooLarge) - } - } - - impl SendMessageFeeProvider for MockErrOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } - } - - pub struct MockTokenIdConvert; - impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::new(1, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))])) - } - fn convert_back(_loc: &Location) -> Option { - None - } - } - - #[test] - fn exporter_validate_with_unknown_network_yields_not_applicable() { - let network = Ethereum { chain_id: 1337 }; - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_invalid_destination_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = None; - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_x8_destination_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Some( - [ - OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, - OnlyChild, - ] - .into(), - ); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_universal_source_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = None; - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_without_global_universal_location_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_global_bridge_location_yields_not_applicable() { - let network = NonBridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Here.into(); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_remote_universal_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Kusama), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_without_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_without_xcm_message_yields_missing_argument() { - let network = BridgedNetwork::get(); - let channel: u32 = 0; - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - let mut destination: Option = Here.into(); - let mut message: Option> = None; - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); - } - - #[test] - fn exporter_validate_with_max_target_fee_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - let assets: Assets = vec![Asset { - id: AssetId(AccountKey20 { network: None, key: token_address }.into()), - fun: Fungible(1000), - }] - .into(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(fees), - BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, - ExpectAsset(fee.into()), - WithdrawAsset(assets), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: Some(network), key: beneficiary_address } - .into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_with_unparsable_xcm_yields_unroutable() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let channel: u32 = 0; - let fee = Asset { id: AssetId(Here.into()), fun: Fungible(1000) }; - let fees: Assets = vec![fee.clone()].into(); - - let mut message: Option> = Some( - vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - } - - #[test] - fn exporter_validate_xcm_success_case_1() { - let network = BridgedNetwork::get(); - let mut destination: Option = Here.into(); - - let mut universal_source: Option = - Some([GlobalConsensus(Polkadot), Parachain(1000)].into()); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee_asset: Asset = Asset { - id: AssetId([AccountKey20 { network: None, key: WETHAddress::get().0 }].into()), - fun: Fungible(1000), - } - .into(); - let filter: AssetFilter = assets.clone().into(); - - let mut message: Option> = Some( - vec![ - WithdrawAsset(assets.clone()), - PayFees { asset: fee_asset }, - WithdrawAsset(assets.clone()), - AliasOrigin(Location::new(1, [GlobalConsensus(Polkadot), Parachain(1000)])), - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(), - ); - - let result = - EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - - assert!(result.is_ok()); - } - - #[test] - fn exporter_deliver_with_submit_failure_yields_unroutable() { - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockErrOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); - assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) - } - - #[test] - fn exporter_validate_with_invalid_dest_does_not_alter_destination() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Parachain(1000).into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(Polkadot), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } - - #[test] - fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { - let network = BridgedNetwork::get(); - let destination: InteriorLocation = Here.into(); - - let universal_source: InteriorLocation = - [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1000)].into(); - - let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); - let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); - - let channel: u32 = 0; - let assets: Assets = vec![Asset { - id: AssetId([AccountKey20 { network: None, key: token_address }].into()), - fun: Fungible(1000), - }] - .into(); - let fee = assets.clone().get(0).unwrap().clone(); - let filter: AssetFilter = assets.clone().into(); - let msg: Xcm<()> = vec![ - WithdrawAsset(assets.clone()), - ClearOrigin, - BuyExecution { fees: fee, weight_limit: Unlimited }, - DepositAsset { - assets: filter, - beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), - }, - SetTopic([0; 32]), - ] - .into(); - let mut msg_wrapper: Option> = Some(msg.clone()); - let mut dest_wrapper = Some(destination.clone()); - let mut universal_source_wrapper = Some(universal_source.clone()); - - let result = EthereumBlobExporter::< - UniversalLocation, - BridgedNetwork, - MockOkOutboundQueue, - AgentIdOf, - MockTokenIdConvert, - WETHAddress, - >::validate( - network, - channel, - &mut universal_source_wrapper, - &mut dest_wrapper, - &mut msg_wrapper, - ); - - assert_eq!(result, Err(XcmSendError::NotApplicable)); - - // ensure mutable variables are not changed - assert_eq!(Some(destination), dest_wrapper); - assert_eq!(Some(msg), msg_wrapper); - assert_eq!(Some(universal_source), universal_source_wrapper); - } -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index 7f2f42792ec0..d375c4a3cc43 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -47,6 +47,7 @@ bridge-hub-westend-runtime = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-router-primitives = { workspace = true } snowbridge-pallet-system = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index a3eaebb59153..8a8e62c5c1b9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -98,6 +98,7 @@ bp-asset-hub-westend = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } @@ -143,6 +144,7 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", @@ -243,6 +245,7 @@ std = [ "primitive-types/std", "scale-info/std", "serde_json/std", + "snowbridge-outbound-router-primitives/std", "snowbridge-router-primitives/std", "sp-api/std", "sp-block-builder/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index d3db7a18a12d..b474b70c1ddc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -715,9 +715,9 @@ pub mod bridging { } pub type EthereumNetworkExportTableV2 = - snowbridge_router_primitives::outbound::v2::XcmFilterExporter< + snowbridge_outbound_router_primitives::v2::XcmFilterExporter< xcm_builder::NetworkExportTable, - snowbridge_router_primitives::outbound::v2::XcmForSnowbridgeV2, + snowbridge_outbound_router_primitives::v2::XcmForSnowbridgeV2, >; pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index daffa32d1b6b..eb4a7d40de6f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -114,6 +114,7 @@ snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-merkle-tree = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } bridge-hub-common = { workspace = true } @@ -193,6 +194,7 @@ std = [ "snowbridge-core/std", "snowbridge-merkle-tree/std", "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-outbound-router-primitives/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue/std", @@ -253,6 +255,7 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", + "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 3d208dc68208..801e6470512e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -24,7 +24,8 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{inbound::v1::MessageToXcm, outbound::v1::EthereumBlobExporter}; +use snowbridge_outbound_router_primitives::v1::EthereumBlobExporter; +use snowbridge_router_primitives::inbound::v1::MessageToXcm; use sp_core::{H160, H256}; use testnet_parachains_constants::rococo::{ currency::*, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 8b2b3b3cfde2..40506e99c6f6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -113,6 +113,7 @@ snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } snowbridge-pallet-outbound-queue-v2 = { workspace = true } snowbridge-outbound-queue-runtime-api-v2 = { workspace = true } @@ -192,6 +193,7 @@ std = [ "snowbridge-merkle-tree/std", "snowbridge-outbound-queue-runtime-api-v2/std", "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-outbound-router-primitives/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue-v2/std", @@ -254,6 +256,7 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", + "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index d9e1ff1a3d3c..a3fed13fe384 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -25,10 +25,10 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{ - inbound::v1::MessageToXcm, - outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, +use snowbridge_outbound_router_primitives::{ + v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2, }; +use snowbridge_router_primitives::inbound::v1::MessageToXcm; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, From e97e23575db92ade68f96c2df1f74722def07a56 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 10:40:03 +0800 Subject: [PATCH 31/81] Clean up --- Cargo.lock | 1 - .../pallets/inbound-queue/src/lib.rs | 2 +- .../pallets/inbound-queue/src/mock.rs | 2 +- bridges/snowbridge/pallets/system/src/lib.rs | 85 +-- bridges/snowbridge/pallets/system/src/mock.rs | 33 +- .../snowbridge/primitives/router/Cargo.toml | 3 - .../primitives/router/src/inbound/mod.rs | 458 ++++++++++++++- .../primitives/router/src/inbound/tests.rs | 83 +++ .../primitives/router/src/inbound/v1.rs | 520 ------------------ .../primitives/router/src/inbound/v2.rs | 520 ------------------ .../src/tests/snowbridge.rs | 3 +- .../src/bridge_to_ethereum_config.rs | 32 +- .../src/bridge_to_ethereum_config.rs | 7 +- 13 files changed, 543 insertions(+), 1206 deletions(-) create mode 100644 bridges/snowbridge/primitives/router/src/inbound/tests.rs delete mode 100644 bridges/snowbridge/primitives/router/src/inbound/v1.rs delete mode 100644 bridges/snowbridge/primitives/router/src/inbound/v2.rs diff --git a/Cargo.lock b/Cargo.lock index 25877bbd36bd..0ec70bb40a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25218,7 +25218,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", - "staging-xcm-builder 7.0.0", "staging-xcm-executor 7.0.0", ] diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index 5814886fe355..423b92b9fae0 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -61,7 +61,7 @@ use snowbridge_core::{ sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, StaticLookup, }; -use snowbridge_router_primitives::inbound::v1::{ +use snowbridge_router_primitives::inbound::{ ConvertMessage, ConvertMessageError, VersionedMessage, }; use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 82862616466d..675d4b691593 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -12,7 +12,7 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, }; -use snowbridge_router_primitives::inbound::v1::MessageToXcm; +use snowbridge_router_primitives::inbound::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index e603e562201f..64b093884622 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -70,7 +70,6 @@ use snowbridge_core::{ meth, outbound::{ v1::{Command, Initializer, Message, SendMessage}, - v2::{Command as CommandV2, Message as MessageV2, SendMessage as SendMessageV2}, OperatingMode, SendError, }, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, @@ -141,7 +140,7 @@ where #[frame_support::pallet] pub mod pallet { use frame_support::dispatch::PostDispatchInfo; - use snowbridge_core::{outbound::v2::second_governance_origin, StaticLookup}; + use snowbridge_core::StaticLookup; use sp_core::U256; use super::*; @@ -156,8 +155,6 @@ pub mod pallet { /// Send messages to Ethereum type OutboundQueue: SendMessage>; - type OutboundQueueV2: SendMessageV2>; - /// Origin check for XCM locations that can create agents type SiblingOrigin: EnsureOrigin; @@ -255,7 +252,6 @@ pub mod pallet { InvalidTokenTransferFees, InvalidPricingParameters, InvalidUpgradeParameters, - TokenAlreadyCreated, } /// The set of registered agents @@ -642,34 +638,6 @@ pub mod pallet { pays_fee: Pays::No, }) } - - /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. - /// Privileged. Can only be called by root. - /// - /// Fee required: No - /// - /// - `origin`: Must be root - /// - `location`: Location of the asset (relative to this chain) - /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum - #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::register_token())] - pub fn register_token_v2( - origin: OriginFor, - location: Box, - metadata: AssetMetadata, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - let location: Location = - (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; - - Self::do_register_token_v2(&location, metadata)?; - - Ok(PostDispatchInfo { - actual_weight: Some(T::WeightInfo::register_token()), - pays_fee: Pays::No, - }) - } } impl Pallet { @@ -795,57 +763,6 @@ pub mod pallet { Ok(()) } - - pub(crate) fn do_register_token_v2( - location: &Location, - metadata: AssetMetadata, - ) -> Result<(), DispatchError> { - let ethereum_location = T::EthereumLocation::get(); - // reanchor to Ethereum context - let location = location - .clone() - .reanchored(ðereum_location, &T::UniversalLocation::get()) - .map_err(|_| Error::::LocationConversionFailed)?; - - let token_id = TokenIdOf::convert_location(&location) - .ok_or(Error::::LocationConversionFailed)?; - - if !ForeignToNativeId::::contains_key(token_id) { - NativeToForeignId::::insert(location.clone(), token_id); - ForeignToNativeId::::insert(token_id, location.clone()); - } - - let command = CommandV2::RegisterForeignToken { - token_id, - name: metadata.name.into_inner(), - symbol: metadata.symbol.into_inner(), - decimals: metadata.decimals, - }; - Self::send_governance_call(second_governance_origin(), command)?; - - Self::deposit_event(Event::::RegisterToken { - location: location.clone().into(), - foreign_token_id: token_id, - }); - - Ok(()) - } - - fn send_governance_call(origin: H256, command: CommandV2) -> DispatchResult { - let message = MessageV2 { - origin, - origin_location: Default::default(), - id: Default::default(), - fee: Default::default(), - commands: BoundedVec::try_from(vec![command]).unwrap(), - }; - - let (ticket, _) = - T::OutboundQueueV2::validate(&message).map_err(|err| Error::::Send(err))?; - - T::OutboundQueueV2::deliver(ticket).map_err(|err| Error::::Send(err))?; - Ok(()) - } } impl StaticLookup for Pallet { diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs index 53ba8e87c140..5b83c0d856b6 100644 --- a/bridges/snowbridge/pallets/system/src/mock.rs +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -11,13 +11,8 @@ use sp_core::H256; use xcm_executor::traits::ConvertLocation; use snowbridge_core::{ - gwei, meth, - outbound::{ - v1::ConstantGasMeter, - v2::{Message, SendMessage}, - SendError as OutboundSendError, SendMessageFeeProvider, - }, - sibling_sovereign_account, AgentId, AllowSiblingsOnly, ParaId, PricingParameters, Rewards, + gwei, meth, outbound::v1::ConstantGasMeter, sibling_sovereign_account, AgentId, + AllowSiblingsOnly, ParaId, PricingParameters, Rewards, }; use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256}, @@ -204,29 +199,6 @@ impl BenchmarkHelper for () { } } -pub struct MockOkOutboundQueue; -impl SendMessage for MockOkOutboundQueue { - type Ticket = (); - - type Balance = u128; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), OutboundSendError> { - Ok(((), 1_u128)) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } -} - -impl SendMessageFeeProvider for MockOkOutboundQueue { - type Balance = u128; - - fn local_fee() -> Self::Balance { - 1 - } -} - impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type OutboundQueue = OutboundQueue; @@ -241,7 +213,6 @@ impl crate::Config for Test { type EthereumLocation = EthereumDestination; #[cfg(feature = "runtime-benchmarks")] type Helper = (); - type OutboundQueueV2 = MockOkOutboundQueue; } // Build genesis storage according to the mock runtime. diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 664f2dbf7930..ee8d481cec12 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -24,7 +24,6 @@ sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -xcm-builder = { workspace = true } snowbridge-core = { workspace = true } @@ -44,7 +43,6 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", - "xcm-builder/std", "xcm-executor/std", "xcm/std", ] @@ -52,6 +50,5 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index abd32aa3897f..e03560f66e24 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -1,16 +1,458 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +//! Converts messages from Ethereum to XCM messages -pub mod v1; -pub mod v2; +#[cfg(test)] +mod tests; -use codec::Encode; -use sp_core::blake2_256; -use sp_std::marker::PhantomData; -use xcm::prelude::{AccountKey20, Ethereum, GlobalConsensus, Location}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_io::hashing::blake2_256; +use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; +use sp_std::prelude::*; +use xcm::prelude::{Junction::AccountKey20, *}; use xcm_executor::traits::ConvertLocation; +const MINIMUM_DEPOSIT: u128 = 1; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V1(MessageV1), +} + +/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct MessageV1 { + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Command { + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Ethereum token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, +} + +/// Destination for bridged tokens +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Destination { + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, +} + +pub struct MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + )>, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The message version is not supported for conversion. + UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, +} + +/// convert the inbound message to xcm which will be forwarded to the destination chain +pub trait ConvertMessage { + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; +} + +pub type CallIndex = [u8; 2]; + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > ConvertMessage + for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + type Balance = Balance; + type AccountId = AccountId; + + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), + } + } +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location = Location::new(2, GlobalConsensus(network)); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + // Forward message id to Asset Hub + SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + message_id: H256, + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + (None, Location::new(0, [AccountId32 { network: None, id }]), 0), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountId32 { network: None, id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountKey20 { network: None, key: id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: Asset = (Location::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); + + instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + dest: Location::new(1, [Parachain(dest_para_id)]), + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + }, + } + + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + // Forwarding to a destination parachain is not allowed for PNA and is validated on the + // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } +} + pub struct EthereumLocationsConverterFor(PhantomData); impl ConvertLocation for EthereumLocationsConverterFor where @@ -35,5 +477,3 @@ impl EthereumLocationsConverterFor { (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) } } - -pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs new file mode 100644 index 000000000000..786aa594f653 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/tests.rs @@ -0,0 +1,83 @@ +use super::EthereumLocationsConverterFor; +use crate::inbound::CallIndex; +use frame_support::{assert_ok, parameter_types}; +use hex_literal::hex; +use xcm::prelude::*; +use xcm_executor::traits::ConvertLocation; + +const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + +parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; +} + +#[test] +fn test_ethereum_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location).unwrap(); + + assert_eq!(account, expected_account); +} + +#[test] +fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("9038d35aba0e78e072d29b2d65be9df5bb4d7d94b4609c9cf98ea8e66e544052"); + let contract_location = Location::new( + 2, + [GlobalConsensus(NETWORK), AccountKey20 { network: None, key: [123u8; 20] }], + ); + + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location).unwrap(); + + assert_eq!(account, expected_account); +} + +#[test] +fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); +} + +#[test] +fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!(reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context)); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs deleted file mode 100644 index 73e5f5ada939..000000000000 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ /dev/null @@ -1,520 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Converts messages from Ethereum to XCM messages - -use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; -use codec::{Decode, Encode}; -use core::marker::PhantomData; -use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; -use scale_info::TypeInfo; -use snowbridge_core::TokenId; -use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; -use sp_std::prelude::*; -use xcm::prelude::{Junction::AccountKey20, *}; - -const MINIMUM_DEPOSIT: u128 = 1; - -/// Messages from Ethereum are versioned. This is because in future, -/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. -/// Instead having BridgeHub transcode the messages into XCM. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum VersionedMessage { - V1(MessageV1), -} - -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are -/// self-contained, in that they can be transcoded using only information in the message. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, - /// The command originating from the Gateway contract - pub command: Command, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, -} - -/// Destination for bridged tokens -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, -} - -pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, -} - -/// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] -pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, -} - -/// convert the inbound message to xcm which will be forwarded to the destination chain -pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > ConvertMessage - for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), - } - } -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > - MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) - } -} - -#[cfg(test)] -mod tests { - use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - - parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; - } - - #[test] - fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - - let account = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); - - assert_eq!(account, expected_account); - } - - #[test] - fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - - assert_eq!( - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), - None, - ); - } - - #[test] - fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!( - reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) - ); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); - } - } -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs deleted file mode 100644 index 73e5f5ada939..000000000000 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ /dev/null @@ -1,520 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork -//! Converts messages from Ethereum to XCM messages - -use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; -use codec::{Decode, Encode}; -use core::marker::PhantomData; -use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; -use scale_info::TypeInfo; -use snowbridge_core::TokenId; -use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; -use sp_std::prelude::*; -use xcm::prelude::{Junction::AccountKey20, *}; - -const MINIMUM_DEPOSIT: u128 = 1; - -/// Messages from Ethereum are versioned. This is because in future, -/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. -/// Instead having BridgeHub transcode the messages into XCM. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum VersionedMessage { - V1(MessageV1), -} - -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are -/// self-contained, in that they can be transcoded using only information in the message. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, - /// The command originating from the Gateway contract - pub command: Command, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, -} - -/// Destination for bridged tokens -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, -} - -pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, -} - -/// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] -pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, -} - -/// convert the inbound message to xcm which will be forwarded to the destination chain -pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > ConvertMessage - for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), - } - } -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > - MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) - } -} - -#[cfg(test)] -mod tests { - use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - - parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; - } - - #[test] - fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - - let account = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); - - assert_eq!(account, expected_account); - } - - #[test] - fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - - assert_eq!( - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), - None, - ); - } - - #[test] - fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!( - reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) - ); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); - } - } -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 6921f0e870f2..6a6809763471 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -22,8 +22,7 @@ use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ - v1::{Command, Destination, MessageV1, VersionedMessage}, - EthereumLocationsConverterFor, + Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 801e6470512e..4af0e08418c8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -25,8 +25,8 @@ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; use snowbridge_outbound_router_primitives::v1::EthereumBlobExporter; -use snowbridge_router_primitives::inbound::v1::MessageToXcm; -use sp_core::{H160, H256}; +use snowbridge_router_primitives::inbound::MessageToXcm; +use sp_core::H160; use testnet_parachains_constants::rococo::{ currency::*, fee::WeightToFee, @@ -38,10 +38,6 @@ use crate::xcm_config::RelayNetwork; use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; -use snowbridge_core::outbound::{ - v2::{Message, SendMessage}, - SendError, SendMessageFeeProvider, -}; use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, FixedU128, @@ -182,29 +178,6 @@ impl snowbridge_pallet_ethereum_client::Config for Runtime { type WeightInfo = crate::weights::snowbridge_pallet_ethereum_client::WeightInfo; } -pub struct DefaultOutboundQueue; -impl SendMessage for DefaultOutboundQueue { - type Ticket = (); - - type Balance = Balance; - - fn validate(_: &Message) -> Result<(Self::Ticket, Self::Balance), SendError> { - Ok(((), Default::default())) - } - - fn deliver(_: Self::Ticket) -> Result { - Ok(H256::zero()) - } -} - -impl SendMessageFeeProvider for DefaultOutboundQueue { - type Balance = Balance; - - fn local_fee() -> Self::Balance { - Default::default() - } -} - impl snowbridge_pallet_system::Config for Runtime { type RuntimeEvent = RuntimeEvent; type OutboundQueue = EthereumOutboundQueue; @@ -219,7 +192,6 @@ impl snowbridge_pallet_system::Config for Runtime { type InboundDeliveryCost = EthereumInboundQueue; type UniversalLocation = UniversalLocation; type EthereumLocation = EthereumLocation; - type OutboundQueueV2 = DefaultOutboundQueue; } #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index a3fed13fe384..4ec6ff5228cf 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -19,8 +19,8 @@ use crate::XcmRouter; use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, - Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumOutboundQueueV2, EthereumSystem, - MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, + Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, + RuntimeEvent, TransactionByteFee, }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; @@ -28,7 +28,7 @@ use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards} use snowbridge_outbound_router_primitives::{ v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2, }; -use snowbridge_router_primitives::inbound::v1::MessageToXcm; +use snowbridge_router_primitives::inbound::MessageToXcm; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -224,7 +224,6 @@ impl snowbridge_pallet_system::Config for Runtime { type InboundDeliveryCost = EthereumInboundQueue; type UniversalLocation = UniversalLocation; type EthereumLocation = EthereumLocation; - type OutboundQueueV2 = EthereumOutboundQueueV2; } #[cfg(feature = "runtime-benchmarks")] From 992740bcd03089111cfc3fd3290393dd1e7d34a5 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 10:54:34 +0800 Subject: [PATCH 32/81] Rename test --- .../bridges/bridge-hub-westend/src/tests/mod.rs | 2 +- .../bridge-hub-westend/src/tests/snowbridge.rs | 14 ++++++++++++-- ...{snowbridge_v2.rs => snowbridge_v2_outbound.rs} | 0 3 files changed, 13 insertions(+), 3 deletions(-) rename cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/{snowbridge_v2.rs => snowbridge_v2_outbound.rs} (100%) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index cd826e3bfb29..4c49614c6a96 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -20,7 +20,7 @@ mod claim_assets; mod register_bridged_assets; mod send_xcm; mod snowbridge; -mod snowbridge_v2; +mod snowbridge_v2_outbound; mod teleport; mod transact; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 6a6809763471..ffa60a4f52e7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -256,6 +256,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); BridgeHubWestend::execute_with(|| { + use bridge_hub_westend_runtime::xcm_config::TreasuryAccount; type RuntimeEvent = ::RuntimeEvent; // Check that the transfer token back to Ethereum message was queue in the Ethereum // Outbound Queue @@ -264,12 +265,21 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { vec![RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued{ .. }) => {},] ); let events = BridgeHubWestend::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == TreasuryAccount::get().into() && *amount == 5071000000 + )), + "Snowbridge sovereign takes local fee." + ); // Check that the remote fee was credited to the AssetHub sovereign account assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Minted { who,.. }) - if *who == assethub_sovereign + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == assethub_sovereign && *amount == 2680000000000, )), "AssetHub sovereign takes remote fee." ); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs similarity index 100% rename from cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs rename to cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs From 0c8badf0b2e44ed727366e53175295855a015d8e Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 13:31:19 +0800 Subject: [PATCH 33/81] Reorgnize code layout --- Cargo.lock | 35 +++++++++++- Cargo.toml | 2 + .../pallets/outbound-queue-v2/Cargo.toml | 5 +- .../outbound-queue-v2/runtime-api/Cargo.toml | 2 + .../outbound-queue-v2/runtime-api/src/lib.rs | 2 +- .../pallets/outbound-queue-v2/src/api.rs | 4 +- .../outbound-queue-v2/src/benchmarking.rs | 6 +- .../pallets/outbound-queue-v2/src/lib.rs | 8 +-- .../pallets/outbound-queue-v2/src/mock.rs | 2 +- .../src/send_message_impl.rs | 2 +- .../pallets/outbound-queue-v2/src/test.rs | 10 ++-- .../pallets/outbound-queue/Cargo.toml | 4 +- .../outbound-queue/runtime-api/Cargo.toml | 2 + .../outbound-queue/runtime-api/src/lib.rs | 6 +- .../pallets/outbound-queue/src/api.rs | 6 +- .../outbound-queue/src/benchmarking.rs | 6 +- .../pallets/outbound-queue/src/lib.rs | 8 +-- .../pallets/outbound-queue/src/mock.rs | 2 +- .../outbound-queue/src/send_message_impl.rs | 10 ++-- .../pallets/outbound-queue/src/test.rs | 10 ++-- bridges/snowbridge/pallets/system/Cargo.toml | 2 + .../pallets/system/src/benchmarking.rs | 3 +- bridges/snowbridge/pallets/system/src/lib.rs | 11 ++-- bridges/snowbridge/pallets/system/src/mock.rs | 5 +- bridges/snowbridge/primitives/core/Cargo.toml | 3 - bridges/snowbridge/primitives/core/src/lib.rs | 1 - .../primitives/core/src/operating_mode.rs | 1 - .../primitives/outbound-router/Cargo.toml | 9 +-- .../primitives/outbound-router/src/lib.rs | 1 - .../primitives/outbound-router/src/v1/mod.rs | 6 +- .../outbound-router/src/v1/tests.rs | 6 +- .../outbound-router/src/v2/convert.rs | 10 ++-- .../primitives/outbound-router/src/v2/mod.rs | 3 +- .../outbound-router/src/v2/tests.rs | 10 ++-- .../snowbridge/primitives/outbound/Cargo.toml | 56 +++++++++++++++++++ .../snowbridge/primitives/outbound/README.md | 4 ++ .../outbound/mod.rs => outbound/src/lib.rs} | 9 +-- .../{core/src/outbound => outbound/src}/v1.rs | 7 +-- .../{core/src/outbound => outbound/src}/v2.rs | 4 +- .../runtime/runtime-common/Cargo.toml | 2 + .../runtime/runtime-common/src/lib.rs | 2 +- .../runtime/runtime-common/src/tests.rs | 2 +- .../bridges/bridge-hub-rococo/Cargo.toml | 1 + .../bridge-hub-rococo/src/tests/snowbridge.rs | 3 +- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge.rs | 3 +- .../src/tests/snowbridge_v2_outbound.rs | 3 +- .../assets/asset-hub-westend/Cargo.toml | 1 - .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 3 +- .../src/bridge_to_ethereum_config.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 6 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 3 +- .../src/bridge_to_ethereum_config.rs | 7 ++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 12 ++-- umbrella/Cargo.toml | 1 - 55 files changed, 206 insertions(+), 129 deletions(-) create mode 100644 bridges/snowbridge/primitives/outbound/Cargo.toml create mode 100644 bridges/snowbridge/primitives/outbound/README.md rename bridges/snowbridge/primitives/{core/src/outbound/mod.rs => outbound/src/lib.rs} (96%) rename bridges/snowbridge/primitives/{core/src/outbound => outbound/src}/v1.rs (99%) rename bridges/snowbridge/primitives/{core/src/outbound => outbound/src}/v2.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 0ec70bb40a92..53c7032a154c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2512,6 +2512,7 @@ dependencies = [ "rococo-westend-system-emulated-network", "scale-info", "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", "snowbridge-pallet-inbound-queue-fixtures 0.10.0", "snowbridge-pallet-outbound-queue 0.2.0", "snowbridge-pallet-system 0.2.0", @@ -2594,6 +2595,7 @@ dependencies = [ "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client 0.2.0", @@ -2753,6 +2755,7 @@ dependencies = [ "rococo-westend-system-emulated-network", "scale-info", "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", "snowbridge-outbound-router-primitives", "snowbridge-pallet-inbound-queue 0.2.0", "snowbridge-pallet-inbound-queue-fixtures 0.10.0", @@ -2835,6 +2838,7 @@ dependencies = [ "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-outbound-router-primitives", @@ -24743,7 +24747,6 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "scale-info", - "serde", "snowbridge-beacon-primitives 0.2.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -24852,6 +24855,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "snowbridge-outbound-primitives" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "ethabi-decode 2.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "hex", + "hex-literal", + "parity-scale-codec", + "polkadot-parachain-primitives 6.0.0", + "scale-info", + "snowbridge-core 0.2.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-std 14.0.0", + "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", +] + [[package]] name = "snowbridge-outbound-queue-merkle-tree" version = "0.9.1" @@ -24872,6 +24898,7 @@ dependencies = [ "parity-scale-codec", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "sp-api 26.0.0", "sp-std 14.0.0", ] @@ -24899,6 +24926,7 @@ dependencies = [ "scale-info", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "sp-api 26.0.0", "sp-std 14.0.0", "staging-xcm 7.0.0", @@ -24914,6 +24942,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -25095,6 +25124,7 @@ dependencies = [ "serde", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", @@ -25144,6 +25174,7 @@ dependencies = [ "serde", "snowbridge-core 0.2.0", "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "snowbridge-outbound-router-primitives", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -25172,6 +25203,7 @@ dependencies = [ "polkadot-primitives 7.0.0", "scale-info", "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", "snowbridge-pallet-outbound-queue 0.2.0", "sp-core 28.0.0", "sp-io 30.0.0", @@ -25249,6 +25281,7 @@ dependencies = [ "log", "parity-scale-codec", "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", "sp-arithmetic 23.0.0", "sp-std 14.0.0", "staging-xcm 7.0.0", diff --git a/Cargo.toml b/Cargo.toml index b753c867b51e..4e6e30552433 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "bridges/snowbridge/primitives/core", "bridges/snowbridge/primitives/ethereum", "bridges/snowbridge/primitives/merkle-tree", + "bridges/snowbridge/primitives/outbound", "bridges/snowbridge/primitives/outbound-router", "bridges/snowbridge/primitives/router", "bridges/snowbridge/runtime/runtime-common", @@ -1226,6 +1227,7 @@ snowbridge-beacon-primitives = { path = "bridges/snowbridge/primitives/beacon", snowbridge-core = { path = "bridges/snowbridge/primitives/core", default-features = false } snowbridge-ethereum = { path = "bridges/snowbridge/primitives/ethereum", default-features = false } snowbridge-merkle-tree = { path = "bridges/snowbridge/primitives/merkle-tree", default-features = false } +snowbridge-outbound-primitives = { path = "bridges/snowbridge/primitives/outbound", default-features = false } snowbridge-outbound-queue-runtime-api = { path = "bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } snowbridge-outbound-queue-runtime-api-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", default-features = false } snowbridge-outbound-router-primitives = { path = "bridges/snowbridge/primitives/outbound-router", default-features = false } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml index ac8dee02f116..1f5c6c84c766 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml @@ -32,11 +32,12 @@ sp-arithmetic = { workspace = true } bridge-hub-common = { workspace = true } -snowbridge-core = { features = ["serde"], workspace = true } +snowbridge-core = { workspace = true } ethabi = { workspace = true } hex-literal = { workspace = true, default-features = true } snowbridge-merkle-tree = { workspace = true } snowbridge-outbound-router-primitives = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } xcm-builder = { workspace = true } @@ -61,6 +62,7 @@ std = [ "serde/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "snowbridge-outbound-router-primitives/std", "sp-arithmetic/std", "sp-core/std", @@ -79,7 +81,6 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", - "snowbridge-outbound-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml index 14f4a8d18c19..8d416b667df1 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/Cargo.toml @@ -22,6 +22,7 @@ sp-api = { workspace = true } frame-support = { workspace = true } snowbridge-core = { workspace = true } snowbridge-merkle-tree = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } xcm = { workspace = true } [features] @@ -32,6 +33,7 @@ std = [ "scale-info/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "sp-api/std", "sp-std/std", "xcm/std", diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index f2c88658c23f..955c37892e7e 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -3,8 +3,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::traits::tokens::Balance as BalanceT; -use snowbridge_core::outbound::{v2::abi::InboundMessage, DryRunError}; use snowbridge_merkle_tree::MerkleProof; +use snowbridge_outbound_primitives::{v2::abi::InboundMessage, DryRunError}; use xcm::prelude::Xcm; sp_api::decl_runtime_apis! { diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 75e51be90112..2b046ed0b883 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -4,14 +4,14 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; -use snowbridge_core::outbound::{ +use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; +use snowbridge_outbound_primitives::{ v2::{ abi::{CommandWrapper, InboundMessage}, GasMeter, Message, }, DryRunError, }; -use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; use snowbridge_outbound_router_primitives::v2::convert::XcmConverter; use sp_core::Get; use sp_std::{default::Default, vec::Vec}; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs index f6e02844a58d..80ce44532921 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/benchmarking.rs @@ -5,10 +5,8 @@ use super::*; use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; use frame_benchmarking::v2::*; -use snowbridge_core::{ - outbound::v1::{Command, Initializer, QueuedMessage}, - ChannelId, -}; +use snowbridge_core::ChannelId; +use snowbridge_outbound_primitives::v1::{Command, Initializer, QueuedMessage}; use sp_core::{H160, H256}; #[allow(unused_imports)] diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 6b669a75e5c9..3fdc838e3039 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -73,13 +73,13 @@ use frame_support::{ pub use pallet::*; use snowbridge_core::{ inbound::{Message as DeliveryMessage, VerificationError, Verifier}, - outbound::v2::{ - abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, - GasMeter, Message, - }, BasicOperatingMode, RewardLedger, TokenId, }; use snowbridge_merkle_tree::merkle_root; +use snowbridge_outbound_primitives::v2::{ + abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, + GasMeter, Message, +}; use sp_core::{H160, H256}; use sp_runtime::{ traits::{BlockNumberProvider, Hash, MaybeEquivalence}, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs index 2215f388b70d..8f3c53c64471 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/mock.rs @@ -14,10 +14,10 @@ use snowbridge_core::{ gwei, inbound::{Log, Proof, VerificationError, Verifier}, meth, - outbound::v2::*, pricing::{PricingParameters, Rewards}, ParaId, }; +use snowbridge_outbound_primitives::v2::*; use sp_core::{ConstU32, H160, H256}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup, Keccak256}, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs index 97188c9c4bc2..6c9a34c3d53a 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -8,7 +8,7 @@ use frame_support::{ ensure, traits::{EnqueueMessage, Get}, }; -use snowbridge_core::outbound::{ +use snowbridge_outbound_primitives::{ v2::{primary_governance_origin, Message, SendMessage}, SendError, SendMessageFeeProvider, }; diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index abbbfd64f54a..8f53485328d1 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -11,12 +11,10 @@ use frame_support::{ }; use codec::Encode; -use snowbridge_core::{ - outbound::{ - v2::{abi::InboundMessageWrapper, primary_governance_origin, Command, SendMessage}, - SendError, - }, - ChannelId, ParaId, +use snowbridge_core::{ChannelId, ParaId}; +use snowbridge_outbound_primitives::{ + v2::{abi::InboundMessageWrapper, primary_governance_origin, Command, SendMessage}, + SendError, }; use sp_core::{hexdisplay::HexDisplay, H256}; diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index 5aa10e69a01e..f0316409ab1e 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -30,7 +30,8 @@ sp-arithmetic = { workspace = true } bridge-hub-common = { workspace = true } -snowbridge-core = { features = ["serde"], workspace = true } +snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } snowbridge-merkle-tree = { workspace = true } ethabi = { workspace = true } @@ -52,6 +53,7 @@ std = [ "serde/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "sp-arithmetic/std", "sp-core/std", "sp-io/std", diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index f050db9378a9..132dcf6235c7 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -21,6 +21,7 @@ sp-api = { workspace = true } frame-support = { workspace = true } snowbridge-merkle-tree = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } [features] default = ["std"] @@ -29,6 +30,7 @@ std = [ "frame-support/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "sp-api/std", "sp-std/std", ] diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs index ecd2de682268..cd25f7169bce 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs @@ -3,11 +3,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::traits::tokens::Balance as BalanceT; -use snowbridge_core::{ - outbound::v1::{Command, Fee}, - PricingParameters, -}; +use snowbridge_core::PricingParameters; use snowbridge_merkle_tree::MerkleProof; +use snowbridge_outbound_primitives::v1::{Command, Fee}; sp_api::decl_runtime_apis! { pub trait OutboundQueueApi where Balance: BalanceT diff --git a/bridges/snowbridge/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs index 08f4f1561968..af2880a67110 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/api.rs @@ -4,11 +4,9 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; -use snowbridge_core::{ - outbound::v1::{Command, Fee, GasMeter}, - PricingParameters, -}; +use snowbridge_core::PricingParameters; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; +use snowbridge_outbound_primitives::v1::{Command, Fee, GasMeter}; use sp_core::Get; pub fn prove_message(leaf_index: u64) -> Option diff --git a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs index 0eff490b1ae4..99e7ce642aac 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs @@ -5,10 +5,8 @@ use super::*; use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; use frame_benchmarking::v2::*; -use snowbridge_core::{ - outbound::v1::{Command, Initializer}, - ChannelId, -}; +use snowbridge_core::ChannelId; +use snowbridge_outbound_primitives::v1::{Command, Initializer}; use sp_core::{H160, H256}; #[allow(unused_imports)] diff --git a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs index feb86bce5dd8..08a8937fbc9b 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -110,11 +110,11 @@ use frame_support::{ traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError}, weights::{Weight, WeightToFee}, }; -use snowbridge_core::{ - outbound::v1::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, - BasicOperatingMode, ChannelId, -}; +use snowbridge_core::{BasicOperatingMode, ChannelId}; use snowbridge_merkle_tree::merkle_root; +use snowbridge_outbound_primitives::v1::{ + Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS, +}; use sp_core::{H256, U256}; use sp_runtime::{ traits::{CheckedDiv, Hash}, diff --git a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs index d7bc4a8bcb5d..aae6bbca3adb 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs @@ -10,10 +10,10 @@ use frame_support::{ use snowbridge_core::{ gwei, meth, - outbound::v1::*, pricing::{PricingParameters, Rewards}, ParaId, PRIMARY_GOVERNANCE_CHANNEL, }; +use snowbridge_outbound_primitives::v1::*; use sp_core::{ConstU32, ConstU8, H160, H256}; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup, Keccak256}, diff --git a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs index 39b41b1c792a..f3b79cdf91c4 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs @@ -10,12 +10,10 @@ use frame_support::{ CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use frame_system::unique; -use snowbridge_core::{ - outbound::{ - v1::{Fee, Message, QueuedMessage, SendMessage, VersionedQueuedMessage}, - SendError, SendMessageFeeProvider, - }, - ChannelId, PRIMARY_GOVERNANCE_CHANNEL, +use snowbridge_core::{ChannelId, PRIMARY_GOVERNANCE_CHANNEL}; +use snowbridge_outbound_primitives::{ + v1::{Fee, Message, QueuedMessage, SendMessage, VersionedQueuedMessage}, + SendError, SendMessageFeeProvider, }; use sp_core::H256; use sp_runtime::BoundedVec; diff --git a/bridges/snowbridge/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs index 36227817f368..7311f48ed8df 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/test.rs @@ -9,12 +9,10 @@ use frame_support::{ }; use codec::Encode; -use snowbridge_core::{ - outbound::{ - v1::{Command, SendMessage}, - SendError, - }, - ParaId, PricingParameters, Rewards, +use snowbridge_core::{ParaId, PricingParameters, Rewards}; +use snowbridge_outbound_primitives::{ + v1::{Command, SendMessage}, + SendError, }; use sp_arithmetic::FixedU128; use sp_core::H256; diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index f1e749afb997..a22f6e3b47bc 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -33,6 +33,7 @@ xcm = { workspace = true } xcm-executor = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } [dev-dependencies] hex = { workspace = true, default-features = true } @@ -53,6 +54,7 @@ std = [ "log/std", "scale-info/std", "snowbridge-core/std", + "snowbridge-outbound-primitives/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/bridges/snowbridge/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs index 939de9d40d13..ec6949ed7036 100644 --- a/bridges/snowbridge/pallets/system/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/system/src/benchmarking.rs @@ -7,7 +7,8 @@ use super::*; use crate::Pallet as SnowbridgeControl; use frame_benchmarking::v2::*; use frame_system::RawOrigin; -use snowbridge_core::{eth, outbound::OperatingMode}; +use snowbridge_core::eth; +use snowbridge_outbound_primitives::OperatingMode; use sp_runtime::SaturatedConversion; use xcm::prelude::*; diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 64b093884622..24575a75b14c 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -67,15 +67,14 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use snowbridge_core::{ - meth, - outbound::{ - v1::{Command, Initializer, Message, SendMessage}, - OperatingMode, SendError, - }, - sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, + meth, sibling_sovereign_account, AgentId, AssetMetadata, Channel, ChannelId, ParaId, PricingParameters as PricingParametersRecord, TokenId, TokenIdOf, PRIMARY_GOVERNANCE_CHANNEL, SECONDARY_GOVERNANCE_CHANNEL, }; +use snowbridge_outbound_primitives::{ + v1::{Command, Initializer, Message, SendMessage}, + OperatingMode, SendError, +}; use sp_core::{RuntimeDebug, H160, H256}; use sp_io::hashing::blake2_256; use sp_runtime::{ diff --git a/bridges/snowbridge/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs index 5b83c0d856b6..1518326797c5 100644 --- a/bridges/snowbridge/pallets/system/src/mock.rs +++ b/bridges/snowbridge/pallets/system/src/mock.rs @@ -11,9 +11,10 @@ use sp_core::H256; use xcm_executor::traits::ConvertLocation; use snowbridge_core::{ - gwei, meth, outbound::v1::ConstantGasMeter, sibling_sovereign_account, AgentId, - AllowSiblingsOnly, ParaId, PricingParameters, Rewards, + gwei, meth, sibling_sovereign_account, AgentId, AllowSiblingsOnly, ParaId, PricingParameters, + Rewards, }; +use snowbridge_outbound_primitives::v1::ConstantGasMeter; use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256}, AccountId32, BuildStorage, FixedU128, diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index 0e696f0d2256..b5863c50805f 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,7 +12,6 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } @@ -50,7 +49,6 @@ std = [ "frame-system/std", "polkadot-parachain-primitives/std", "scale-info/std", - "serde/std", "snowbridge-beacon-primitives/std", "sp-arithmetic/std", "sp-core/std", @@ -60,7 +58,6 @@ std = [ "xcm-builder/std", "xcm/std", ] -serde = ["dep:serde", "scale-info/serde"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 88ac8124a15b..e3bfb34897d6 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -11,7 +11,6 @@ mod tests; pub mod inbound; pub mod location; pub mod operating_mode; -pub mod outbound; pub mod pricing; pub mod reward; pub mod ringbuffer; diff --git a/bridges/snowbridge/primitives/core/src/operating_mode.rs b/bridges/snowbridge/primitives/core/src/operating_mode.rs index 9894e587ef5e..8957bc6cc45e 100644 --- a/bridges/snowbridge/primitives/core/src/operating_mode.rs +++ b/bridges/snowbridge/primitives/core/src/operating_mode.rs @@ -4,7 +4,6 @@ use sp_runtime::RuntimeDebug; /// Basic operating modes for a bridges module (Normal/Halted). #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BasicOperatingMode { /// Normal mode, when all operations are allowed. Normal, diff --git a/bridges/snowbridge/primitives/outbound-router/Cargo.toml b/bridges/snowbridge/primitives/outbound-router/Cargo.toml index 17601d440973..5eb9e703cbc0 100644 --- a/bridges/snowbridge/primitives/outbound-router/Cargo.toml +++ b/bridges/snowbridge/primitives/outbound-router/Cargo.toml @@ -27,6 +27,7 @@ xcm-executor = { workspace = true } xcm-builder = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } hex-literal = { workspace = true, default-features = true } @@ -40,6 +41,7 @@ std = [ "log/std", "scale-info/std", "snowbridge-core/std", + "snowbridge-outbound-primitives/std", "sp-core/std", "sp-io/std", "sp-runtime/std", @@ -48,10 +50,3 @@ std = [ "xcm-executor/std", "xcm/std", ] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "snowbridge-core/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", -] diff --git a/bridges/snowbridge/primitives/outbound-router/src/lib.rs b/bridges/snowbridge/primitives/outbound-router/src/lib.rs index 7ab04608543d..f497ef3742a0 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/lib.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/lib.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork #![cfg_attr(not(feature = "std"), no_std)] - pub mod v1; pub mod v2; diff --git a/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs index 6394ba927d8a..e5f274c1eaac 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v1/mod.rs @@ -10,10 +10,8 @@ use core::slice::Iter; use codec::{Decode, Encode}; use frame_support::{ensure, traits::Get}; -use snowbridge_core::{ - outbound::v1::{AgentExecuteCommand, Command, Message, SendMessage}, - AgentId, ChannelId, ParaId, TokenId, TokenIdOf, -}; +use snowbridge_core::{AgentId, ChannelId, ParaId, TokenId, TokenIdOf}; +use snowbridge_outbound_primitives::v1::{AgentExecuteCommand, Command, Message, SendMessage}; use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; diff --git a/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs b/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs index 607e2ea611a4..ad889fbd5d35 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v1/tests.rs @@ -1,9 +1,7 @@ use frame_support::parameter_types; use hex_literal::hex; -use snowbridge_core::{ - outbound::{v1::Fee, SendError, SendMessageFeeProvider}, - AgentIdOf, -}; +use snowbridge_core::AgentIdOf; +use snowbridge_outbound_primitives::{v1::Fee, SendError, SendMessageFeeProvider}; use sp_std::default::Default; use xcm::{ latest::{ROCOCO_GENESIS_HASH, WESTEND_GENESIS_HASH}, diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs index 8253322c34d5..25ecdcee3bc6 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -5,12 +5,10 @@ use codec::DecodeAll; use core::slice::Iter; use frame_support::{ensure, traits::Get, BoundedVec}; -use snowbridge_core::{ - outbound::{ - v2::{Command, Message}, - TransactInfo, - }, - TokenId, TokenIdOf, TokenIdOf as LocationIdOf, +use snowbridge_core::{TokenId, TokenIdOf, TokenIdOf as LocationIdOf}; +use snowbridge_outbound_primitives::{ + v2::{Command, Message}, + TransactInfo, }; use sp_core::H160; use sp_runtime::traits::MaybeEquivalence; diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs index fe719e68ea04..eeffc7361d34 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs @@ -13,7 +13,8 @@ use frame_support::{ ensure, traits::{Contains, Get, ProcessMessageError}, }; -use snowbridge_core::{outbound::v2::SendMessage, TokenId}; +use snowbridge_core::TokenId; +use snowbridge_outbound_primitives::v2::SendMessage; use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::{marker::PhantomData, ops::ControlFlow, prelude::*}; diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs index 835c7abc59aa..e5eaba48c179 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs @@ -2,12 +2,10 @@ use super::*; use crate::v2::convert::XcmConverterError; use frame_support::{parameter_types, BoundedVec}; use hex_literal::hex; -use snowbridge_core::{ - outbound::{ - v2::{Command, Message}, - SendError, SendMessageFeeProvider, - }, - AgentIdOf, TokenIdOf, +use snowbridge_core::{AgentIdOf, TokenIdOf}; +use snowbridge_outbound_primitives::{ + v2::{Command, Message}, + SendError, SendMessageFeeProvider, }; use sp_std::default::Default; use xcm::{latest::WESTEND_GENESIS_HASH, prelude::SendError as XcmSendError}; diff --git a/bridges/snowbridge/primitives/outbound/Cargo.toml b/bridges/snowbridge/primitives/outbound/Cargo.toml new file mode 100644 index 000000000000..87af3fb3ffe5 --- /dev/null +++ b/bridges/snowbridge/primitives/outbound/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "snowbridge-outbound-primitives" +description = "Snowbridge outbound primitives" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[dependencies] +codec = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +hex-literal = { workspace = true, default-features = true } + +polkadot-parachain-primitives = { workspace = true } +xcm = { workspace = true } +xcm-builder = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-arithmetic = { workspace = true } + +ethabi = { workspace = true } +alloy-primitives = { features = ["rlp"], workspace = true } +alloy-sol-types = { workspace = true } + +snowbridge-core = { workspace = true } + +[dev-dependencies] +hex = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-sol-types/std", + "codec/std", + "ethabi/std", + "frame-support/std", + "frame-system/std", + "polkadot-parachain-primitives/std", + "scale-info/std", + "snowbridge-core/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-std/std", + "xcm-builder/std", + "xcm/std", +] diff --git a/bridges/snowbridge/primitives/outbound/README.md b/bridges/snowbridge/primitives/outbound/README.md new file mode 100644 index 000000000000..0126be63aeba --- /dev/null +++ b/bridges/snowbridge/primitives/outbound/README.md @@ -0,0 +1,4 @@ +# Core Primitives + +Contains common code core to Snowbridge, such as inbound and outbound queue types, pricing structs, ringbuffer data +types (used in the beacon client). diff --git a/bridges/snowbridge/primitives/core/src/outbound/mod.rs b/bridges/snowbridge/primitives/outbound/src/lib.rs similarity index 96% rename from bridges/snowbridge/primitives/core/src/outbound/mod.rs rename to bridges/snowbridge/primitives/outbound/src/lib.rs index 972f16fb2139..6a4c21d501d4 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/outbound/src/lib.rs @@ -1,17 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] //! # Outbound //! //! Common traits and types -use crate::Vec; +pub mod v1; +pub mod v2; + use codec::{Decode, Encode}; use frame_support::PalletError; use scale_info::TypeInfo; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_core::{RuntimeDebug, H160}; - -pub mod v1; -pub mod v2; +use sp_std::vec::Vec; /// The operating mode of Channels and Gateway contract on Ethereum. #[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] diff --git a/bridges/snowbridge/primitives/core/src/outbound/v1.rs b/bridges/snowbridge/primitives/outbound/src/v1.rs similarity index 99% rename from bridges/snowbridge/primitives/core/src/outbound/v1.rs rename to bridges/snowbridge/primitives/outbound/src/v1.rs index 037fc21db017..b35e55d524e2 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v1.rs +++ b/bridges/snowbridge/primitives/outbound/src/v1.rs @@ -2,14 +2,11 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! # Outbound V1 primitives -use crate::{ - outbound::{OperatingMode, SendError, SendMessageFeeProvider}, - pricing::UD60x18, - ChannelId, -}; +use crate::{OperatingMode, SendError, SendMessageFeeProvider}; use codec::{Decode, Encode}; use ethabi::Token; use scale_info::TypeInfo; +use snowbridge_core::{pricing::UD60x18, ChannelId}; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_core::{RuntimeDebug, H160, H256, U256}; use sp_std::{borrow::ToOwned, vec, vec::Vec}; diff --git a/bridges/snowbridge/primitives/core/src/outbound/v2.rs b/bridges/snowbridge/primitives/outbound/src/v2.rs similarity index 99% rename from bridges/snowbridge/primitives/core/src/outbound/v2.rs rename to bridges/snowbridge/primitives/outbound/src/v2.rs index a45fcc9eb261..4b0add908528 100644 --- a/bridges/snowbridge/primitives/core/src/outbound/v2.rs +++ b/bridges/snowbridge/primitives/outbound/src/v2.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! # Outbound V2 primitives -use crate::outbound::{OperatingMode, SendError}; use codec::{Decode, Encode}; use frame_support::{pallet_prelude::ConstU32, BoundedVec}; use hex_literal::hex; @@ -11,7 +10,8 @@ use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_core::{RuntimeDebug, H160, H256}; use sp_std::{vec, vec::Vec}; -use crate::outbound::v2::abi::{ +use crate::{OperatingMode, SendError}; +use abi::{ CallContractParams, MintForeignTokenParams, RegisterForeignTokenParams, SetOperatingModeParams, UnlockNativeTokenParams, UpgradeParams, }; diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml index d47cb3cb7101..946932e5d7f9 100644 --- a/bridges/snowbridge/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -22,6 +22,7 @@ xcm-builder = { workspace = true } xcm-executor = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } [dev-dependencies] @@ -32,6 +33,7 @@ std = [ "frame-support/std", "log/std", "snowbridge-core/std", + "snowbridge-outbound-primitives/std", "sp-arithmetic/std", "sp-std/std", "xcm-builder/std", diff --git a/bridges/snowbridge/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs index 0b1a74b232a0..314156b367b0 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/lib.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/lib.rs @@ -11,7 +11,7 @@ mod tests; use codec::FullCodec; use core::marker::PhantomData; use frame_support::traits::Get; -use snowbridge_core::outbound::SendMessageFeeProvider; +use snowbridge_outbound_primitives::SendMessageFeeProvider; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_std::fmt::Debug; use xcm::prelude::*; diff --git a/bridges/snowbridge/runtime/runtime-common/src/tests.rs b/bridges/snowbridge/runtime/runtime-common/src/tests.rs index dea5ad5411c2..72f86d255b4c 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/tests.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/tests.rs @@ -1,6 +1,6 @@ use crate::XcmExportFeeToSibling; use frame_support::{parameter_types, sp_runtime::testing::H256}; -use snowbridge_core::outbound::{ +use snowbridge_outbound_primitives::{ v1::{Fee, Message, SendMessage}, SendError, SendMessageFeeProvider, }; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml index 9f6fe78a33ee..ea8a986fcd59 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -45,6 +45,7 @@ testnet-parachains-constants = { features = ["rococo", "westend"], workspace = t # Snowbridge snowbridge-core = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } snowbridge-pallet-system = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index d59553574c26..967dc43407be 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -18,7 +18,8 @@ use emulated_integration_tests_common::xcm_emulator::ConvertLocation; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::BridgeHubRococoParaSender as BridgeHubRococoSender; -use snowbridge_core::{inbound::InboundQueueFixture, outbound::OperatingMode}; +use snowbridge_core::inbound::InboundQueueFixture; +use snowbridge_outbound_primitives::OperatingMode; use snowbridge_pallet_inbound_queue_fixtures::{ register_token::make_register_token_message, send_token::make_send_token_message, send_token_to_penpal::make_send_token_to_penpal_message, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index d375c4a3cc43..fde1e29f9d23 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -47,6 +47,7 @@ bridge-hub-westend-runtime = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } snowbridge-outbound-router-primitives = { workspace = true } snowbridge-pallet-system = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e7..bee5665d56ce 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -20,7 +20,8 @@ use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; -use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; +use snowbridge_core::{AssetMetadata, TokenIdOf}; +use snowbridge_outbound_primitives::OperatingMode; use snowbridge_router_primitives::inbound::{ Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index b07f7faf554c..21e752a981a2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -15,7 +15,8 @@ use crate::imports::*; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; -use snowbridge_core::{outbound::TransactInfo, AssetMetadata}; +use snowbridge_core::AssetMetadata; +use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use sp_runtime::MultiAddress; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 8a8e62c5c1b9..b5ef949febdf 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -144,7 +144,6 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", - "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index eb4a7d40de6f..a3d3e682801e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -114,6 +114,7 @@ snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-merkle-tree = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } snowbridge-outbound-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } @@ -193,6 +194,7 @@ std = [ "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "snowbridge-outbound-queue-runtime-api/std", "snowbridge-outbound-router-primitives/std", "snowbridge-pallet-ethereum-client/std", @@ -255,7 +257,6 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", - "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index 4af0e08418c8..98d7db2ad08e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -108,7 +108,7 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Decimals = ConstU8<12>; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::v1::ConstantGasMeter; + type GasMeter = crate::ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index e19f9853cb22..a090d1e9799c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -91,10 +91,8 @@ pub use sp_runtime::BuildStorage; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use rococo_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; -use snowbridge_core::{ - outbound::v1::{Command, Fee}, - AgentId, PricingParameters, -}; +use snowbridge_core::{AgentId, PricingParameters}; +pub use snowbridge_outbound_primitives::v1::{Command, ConstantGasMeter, Fee}; use xcm::{latest::prelude::*, prelude::*}; use xcm_runtime_apis::{ dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects}, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 40506e99c6f6..dc5ec22ad231 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -113,6 +113,7 @@ snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } snowbridge-outbound-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } snowbridge-pallet-outbound-queue-v2 = { workspace = true } @@ -191,6 +192,7 @@ std = [ "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-merkle-tree/std", + "snowbridge-outbound-primitives/std", "snowbridge-outbound-queue-runtime-api-v2/std", "snowbridge-outbound-queue-runtime-api/std", "snowbridge-outbound-router-primitives/std", @@ -256,7 +258,6 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", - "snowbridge-outbound-router-primitives/runtime-benchmarks", "snowbridge-pallet-ethereum-client/runtime-benchmarks", "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 4ec6ff5228cf..f1b800824fe2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -25,6 +25,9 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; +use snowbridge_outbound_primitives::{ + v1::ConstantGasMeter, v2::ConstantGasMeter as ConstantGasMeterV2, +}; use snowbridge_outbound_router_primitives::{ v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2, }; @@ -125,7 +128,7 @@ impl snowbridge_pallet_outbound_queue::Config for Runtime { type Decimals = ConstU8<12>; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::v1::ConstantGasMeter; + type GasMeter = ConstantGasMeter; type Balance = Balance; type WeightToFee = WeightToFee; type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo; @@ -139,7 +142,7 @@ impl snowbridge_pallet_outbound_queue_v2::Config for Runtime { type MessageQueue = MessageQueue; type MaxMessagePayloadSize = ConstU32<2048>; type MaxMessagesPerBlock = ConstU32<32>; - type GasMeter = snowbridge_core::outbound::v2::ConstantGasMeter; + type GasMeter = ConstantGasMeterV2; type Balance = Balance; type WeightToFee = WeightToFee; type Verifier = snowbridge_pallet_ethereum_client::Pallet; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 96faab57f687..bf91526ab079 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -98,13 +98,11 @@ use parachains_common::{ impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; -use snowbridge_core::{ - outbound::{ - v1::{Command, Fee}, - v2::abi::InboundMessage, - DryRunError, - }, - AgentId, PricingParameters, +use snowbridge_core::{AgentId, PricingParameters}; +use snowbridge_outbound_primitives::{ + v1::{Command, Fee}, + v2::abi::InboundMessage, + DryRunError, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use westend_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 31e7e9fea3a4..664b3a9e46f1 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -504,7 +504,6 @@ serde = [ "pallet-treasury?/serde", "pallet-xcm?/serde", "snowbridge-beacon-primitives?/serde", - "snowbridge-core?/serde", "snowbridge-ethereum?/serde", "snowbridge-pallet-ethereum-client?/serde", "snowbridge-pallet-inbound-queue?/serde", From 10dcaf57980e6c291b8a968bd8605bcb1e30374f Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 16:26:01 +0800 Subject: [PATCH 34/81] More cleanup --- Cargo.lock | 3 +-- bridges/snowbridge/primitives/core/Cargo.toml | 7 +++---- bridges/snowbridge/primitives/core/src/operating_mode.rs | 1 + 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53c7032a154c..94941beb99f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24737,8 +24737,6 @@ dependencies = [ name = "snowbridge-core" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", "ethabi-decode 2.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -24747,6 +24745,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "scale-info", + "serde", "snowbridge-beacon-primitives 0.2.0", "sp-arithmetic 23.0.0", "sp-core 28.0.0", diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index b5863c50805f..fa37c795b2d1 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,6 +12,7 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] +serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } @@ -31,8 +32,6 @@ sp-arithmetic = { workspace = true } snowbridge-beacon-primitives = { workspace = true } ethabi = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } [dev-dependencies] hex = { workspace = true, default-features = true } @@ -41,14 +40,13 @@ xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", "codec/std", "ethabi/std", "frame-support/std", "frame-system/std", "polkadot-parachain-primitives/std", "scale-info/std", + "serde/std", "snowbridge-beacon-primitives/std", "sp-arithmetic/std", "sp-core/std", @@ -58,6 +56,7 @@ std = [ "xcm-builder/std", "xcm/std", ] +serde = ["dep:serde", "scale-info/serde"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/bridges/snowbridge/primitives/core/src/operating_mode.rs b/bridges/snowbridge/primitives/core/src/operating_mode.rs index 8957bc6cc45e..9894e587ef5e 100644 --- a/bridges/snowbridge/primitives/core/src/operating_mode.rs +++ b/bridges/snowbridge/primitives/core/src/operating_mode.rs @@ -4,6 +4,7 @@ use sp_runtime::RuntimeDebug; /// Basic operating modes for a bridges module (Normal/Halted). #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum BasicOperatingMode { /// Normal mode, when all operations are allowed. Normal, From 8dfa3c5e1d21e980b97aa15f431f03655afc053b Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 4 Dec 2024 11:17:52 +0800 Subject: [PATCH 35/81] Add force_create_agent --- bridges/snowbridge/pallets/system/src/lib.rs | 34 +++++++ .../src/tests/snowbridge_v2_outbound.rs | 95 +++++-------------- 2 files changed, 58 insertions(+), 71 deletions(-) diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index 24575a75b14c..c324459a8656 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -637,6 +637,40 @@ pub mod pallet { pays_fee: Pays::No, }) } + + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn force_create_agent( + origin: OriginFor, + location: Box, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + let location: Location = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + let ethereum_location = T::EthereumLocation::get(); + let location = location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let agent_id = agent_id_of::(&location)?; + + // Record the agent id or fail if it has already been created + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + + let command = Command::CreateAgent { agent_id }; + let pays_fee = PaysFee::::No; + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::CreateAgent { location: Box::new(location), agent_id }); + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::register_token()), + pays_fee: Pays::No, + }) + } } impl Pallet { diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 21e752a981a2..658612ed2ddc 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -18,7 +18,6 @@ use hex_literal::hex; use snowbridge_core::AssetMetadata; use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; -use sp_runtime::MultiAddress; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; @@ -95,11 +94,6 @@ pub fn register_relay_token() { type RuntimeOrigin = ::RuntimeOrigin; // Register WND on BH - assert_ok!(::Balances::force_set_balance( - RuntimeOrigin::root(), - MultiAddress::Id(BridgeHubWestendSender::get()), - INITIAL_FUND, - )); assert_ok!(::EthereumSystem::register_token( RuntimeOrigin::root(), Box::new(VersionedLocation::from(Location::parent())), @@ -325,71 +319,30 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { }); } -// #[test] -// fn create_agent() { -// fund_sovereign(); -// -// register_weth(); -// -// BridgeHubWestend::execute_with(|| {}); -// -// AssetHubWestend::execute_with(|| { -// type RuntimeOrigin = ::RuntimeOrigin; -// -// let local_fee_asset = -// Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; -// -// // All WETH as fee and reserve_asset is zero, so there is no transfer in this case -// let remote_fee_asset = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT) }; -// let reserve_asset = Asset { id: AssetId(weth_location()), fun: Fungible(0) }; -// -// let assets = vec![ -// Asset { id: weth_location().into(), fun: Fungible(TOKEN_AMOUNT) }, -// local_fee_asset.clone(), -// ]; -// -// let transact_info = TransactInfo { kind: TransactKind::RegisterAgent, params: vec![] }; -// -// let xcms = VersionedXcm::from(Xcm(vec![ -// WithdrawAsset(assets.clone().into()), -// PayFees { asset: local_fee_asset.clone() }, -// InitiateTransfer { -// destination: destination(), -// remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( -// remote_fee_asset.clone().into(), -// ))), -// preserve_origin: true, -// assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( -// reserve_asset.clone().into(), -// ))], -// remote_xcm: Xcm(vec![ -// DepositAsset { assets: Wild(AllCounted(2)), beneficiary: beneficiary() }, -// Transact { -// origin_kind: OriginKind::SovereignAccount, -// call: transact_info.encode().into(), -// }, -// ]), -// }, -// ])); -// -// // Send the Weth back to Ethereum -// ::PolkadotXcm::execute( -// RuntimeOrigin::signed(AssetHubWestendReceiver::get()), -// bx!(xcms), -// Weight::from(8_000_000_000), -// ) -// .unwrap(); -// }); -// -// BridgeHubWestend::execute_with(|| { -// type RuntimeEvent = ::RuntimeEvent; -// // Check that Ethereum message was queue in the Outbound Queue -// assert_expected_events!( -// BridgeHubWestend, -// vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageAccepted{ .. }) => {},] -// ); -// }); -// } +#[test] +fn create_agent() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { network: None, id: AssetHubWestendSender::get().into() }, + ], + ); + + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); +} #[test] fn transact_with_agent() { From f540b5265d3ef62fa58ba75a5d6c7e10ed3d7fec Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Dec 2024 01:37:44 +0800 Subject: [PATCH 36/81] Smoke test for multiple-hop --- .../outbound-router/src/v2/convert.rs | 4 +- .../src/tests/snowbridge.rs | 2 +- .../src/tests/snowbridge_v2_outbound.rs | 357 ++++++++++++++++-- 3 files changed, 331 insertions(+), 32 deletions(-) diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs index 25ecdcee3bc6..96eeea06bc8b 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -5,7 +5,7 @@ use codec::DecodeAll; use core::slice::Iter; use frame_support::{ensure, traits::Get, BoundedVec}; -use snowbridge_core::{TokenId, TokenIdOf, TokenIdOf as LocationIdOf}; +use snowbridge_core::{AgentIdOf, TokenId, TokenIdOf}; use snowbridge_outbound_primitives::{ v2::{Command, Message}, TransactInfo, @@ -148,7 +148,7 @@ where // Check AliasOrigin. let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin) .ok_or(AliasOriginExpected)?; - let origin = LocationIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?; + let origin = AgentIdOf::convert_location(origin_location).ok_or(InvalidOrigin)?; let (deposit_assets, beneficiary) = match_expression!( self.next()?, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index bee5665d56ce..206005c9890c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -31,7 +31,7 @@ use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; -pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +pub const WETH: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 658612ed2ddc..3be500e98953 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -12,9 +12,23 @@ // 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::imports::*; +use crate::{ + create_pool_with_native_on, + imports::*, + tests::snowbridge::{CHAIN_ID, WETH}, +}; +use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; +use rococo_westend_system_emulated_network::{ + bridge_hub_rococo_emulated_chain::genesis::ASSETHUB_PARA_ID, + penpal_emulated_chain::{ + penpal_runtime::xcm_config::{ + CheckingAccount, LocalTeleportableToAssetHub, TELEPORTABLE_ASSET_ID, + }, + PenpalAssetOwner, + }, +}; use snowbridge_core::AssetMetadata; use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; @@ -23,13 +37,11 @@ use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 50_000_000_000_000; -pub const CHAIN_ID: u64 = 11155111; -pub const WETH: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); -const TOKEN_AMOUNT: u128 = 100_000_000_000; -const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 4_000_000_000; -const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 200_000_000_000; +const TOKEN_AMOUNT: u128 = 1_000_000_000_000; +const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 400_000_000_000; +const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 800_000_000_000; const EXECUTION_WEIGHT: u64 = 8_000_000_000; @@ -43,7 +55,7 @@ pub fn weth_location() -> Location { ) } -pub fn destination() -> Location { +pub fn ethereum() -> Location { Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]) } @@ -51,23 +63,33 @@ pub fn beneficiary() -> Location { Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) } -pub fn fund_sovereign() { +pub fn asset_hub() -> Location { + Location::new(1, Parachain(ASSETHUB_PARA_ID)) +} + +pub fn fund_on_bh() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); } -pub fn register_weth() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); +pub fn register_weth_on_ah() { + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; assert_ok!(::ForeignAssets::force_create( RuntimeOrigin::root(), weth_location().try_into().unwrap(), - assethub_sovereign.clone().into(), - false, + ethereum_sovereign.clone().into(), + true, 1, )); @@ -110,11 +132,176 @@ pub fn register_relay_token() { }); } +pub fn register_weth_on_penpal() { + PenpalB::execute_with(|| { + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + ethereum_sovereign.into(), + true, + 1, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + }); +} + +pub fn register_pal_on_ah() { + // Create PAL(i.e. native asset for penpal) on AH. + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + let penpal_asset_id = Location::new(1, Parachain(PenpalB::para_id().into())); + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + penpal_asset_id.clone(), + PenpalAssetOwner::get().into(), + false, + 1_000_000, + )); + + assert!(::ForeignAssets::asset_exists( + penpal_asset_id.clone(), + )); + + assert_ok!(::ForeignAssets::mint_into( + penpal_asset_id.clone(), + &AssetHubWestendReceiver::get(), + TOKEN_AMOUNT, + )); + + assert_ok!(::ForeignAssets::mint_into( + penpal_asset_id.clone(), + &AssetHubWestendSender::get(), + TOKEN_AMOUNT, + )); + }); +} + +pub fn fund_on_penpal() { + PenpalB::fund_accounts(vec![ + (PenpalBReceiver::get(), INITIAL_FUND), + (PenpalBSender::get(), INITIAL_FUND), + (CheckingAccount::get(), INITIAL_FUND), + ]); + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + }); + PenpalB::execute_with(|| { + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + }); +} + +pub fn set_trust_reserve_on_penpal() { + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); +} + +pub fn fund_on_ah() { + let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + + AssetHubWestend::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &penpal_sovereign, + TOKEN_AMOUNT, + )); + }); + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn create_pools() { + // We create a pool between WND and WETH in AssetHub to support paying for fees with WETH. + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + create_pool_with_native_on!(AssetHubWestend, weth_location(), true, ethereum_sovereign.clone()); + // We also need a pool between WND and WETH on PenpalB to support paying for fees with WETH. + create_pool_with_native_on!(PenpalB, weth_location(), true, ethereum_sovereign.clone()); +} + +pub fn register_pal_on_bh() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(PenpalBTeleportableAssetLocation::get())), + AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + }); +} + #[test] fn send_weth_from_asset_hub_to_ethereum() { - fund_sovereign(); + fund_on_bh(); - register_weth(); + register_weth_on_ah(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -139,7 +326,7 @@ fn send_weth_from_asset_hub_to_ethereum() { WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination: destination(), + destination: ethereum(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -176,13 +363,13 @@ fn send_weth_from_asset_hub_to_ethereum() { #[test] fn transfer_relay_token() { let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&destination()) + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(ðereum()) .unwrap() .into(); - fund_sovereign(); + fund_on_bh(); - register_weth(); + register_weth_on_ah(); register_relay_token(); @@ -208,7 +395,7 @@ fn transfer_relay_token() { WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination: destination(), + destination: ethereum(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -256,9 +443,9 @@ fn transfer_relay_token() { #[test] fn send_weth_and_dot_from_asset_hub_to_ethereum() { - fund_sovereign(); + fund_on_bh(); - register_weth(); + register_weth_on_ah(); register_relay_token(); @@ -285,7 +472,7 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination: destination(), + destination: ethereum(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -333,9 +520,11 @@ fn create_agent() { ], ); - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) ); assert_expected_events!( BridgeHubWestend, @@ -349,9 +538,9 @@ fn transact_with_agent() { let weth_asset_location: Location = (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); - fund_sovereign(); + fund_on_bh(); - register_weth(); + register_weth_on_ah(); BridgeHubWestend::execute_with(|| {}); @@ -390,7 +579,7 @@ fn transact_with_agent() { WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset.clone() }, InitiateTransfer { - destination: destination(), + destination: ethereum(), remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset.clone().into(), ))), @@ -425,3 +614,113 @@ fn transact_with_agent() { ); }); } + +#[test] +fn send_penpal_native_asset_to_ethereum() { + fund_on_bh(); + register_weth_on_ah(); + register_pal_on_ah(); + register_pal_on_bh(); + fund_on_ah(); + fund_on_penpal(); + register_weth_on_penpal(); + set_trust_reserve_on_penpal(); + create_pools(); + + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_asset_on_penpal = + Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }; + + let remote_fee_asset_on_ah = + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; + + let remote_fee_asset_on_ethereum = + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; + + let transfer_asset = + Asset { id: AssetId(LocalTeleportableToAssetHub::get()), fun: Fungible(TOKEN_AMOUNT) }; + + let transfer_asset_reanchor_on_ah = Asset { + id: AssetId(PenpalBTeleportableAssetLocation::get()), + fun: Fungible(TOKEN_AMOUNT), + }; + + let assets = vec![ + local_fee_asset_on_penpal.clone(), + remote_fee_asset_on_ah.clone(), + remote_fee_asset_on_ethereum.clone(), + transfer_asset.clone(), + ]; + + let transact_info = + TransactInfo { target: Default::default(), data: vec![], gas_limit: 40000, value: 0 }; + + let xcms = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset_on_penpal.clone() }, + InitiateTransfer { + destination: asset_hub(), + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset_on_ah.clone().into(), + ))), + preserve_origin: true, + assets: vec![ + AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset_on_ethereum.clone().into(), + )), + // Should use Teleport here because: + // a. Penpal is configured to allow teleport specific asset to AH + // b. AH is configured to trust asset teleport from sibling chain + AssetTransferFilter::Teleport(Definite(transfer_asset.clone().into())), + ], + remote_xcm: Xcm(vec![InitiateTransfer { + destination: ethereum(), + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset_on_ethereum.clone().into(), + ))), + preserve_origin: true, + // should use ReserveDeposit because Ethereum does not trust asset from penpal. + // transfer_asset should be reachored first on AH + assets: vec![AssetTransferFilter::ReserveDeposit(Definite( + transfer_asset_reanchor_on_ah.clone().into(), + ))], + remote_xcm: Xcm(vec![ + DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, + Transact { + origin_kind: OriginKind::SovereignAccount, + call: transact_info.encode().into(), + }, + ]), + }]), + }, + ])); + + assert_ok!(::PolkadotXcm::execute( + RuntimeOrigin::signed(PenpalBSender::get()), + bx!(xcms), + Weight::from(EXECUTION_WEIGHT), + )); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapCreditExecuted { .. }) => {},] + ); + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + }); +} From 1befeed826bbf76cc1e6bbe62451506e1f11cb60 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Dec 2024 19:12:48 +0800 Subject: [PATCH 37/81] Fix with more tests --- .../outbound-router/src/v2/convert.rs | 11 +- .../src/tests/snowbridge_v2_outbound.rs | 276 ++++++++++++------ .../runtimes/testing/penpal/src/xcm_config.rs | 34 ++- 3 files changed, 229 insertions(+), 92 deletions(-) diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs index 96eeea06bc8b..1e5a84ba0e81 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -130,7 +130,7 @@ where let fee_amount = self.extract_remote_fee()?; // Get ENA reserve asset from WithdrawAsset. - let enas = + let mut enas = match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets); if enas.is_some() { let _ = self.next(); @@ -145,6 +145,15 @@ where if pnas.is_some() { let _ = self.next(); } + + // Try to get ENA again if it is after PNA + if enas.is_none() { + enas = + match_expression!(self.peek(), Ok(WithdrawAsset(reserve_assets)), reserve_assets); + if enas.is_some() { + let _ = self.next(); + } + } // Check AliasOrigin. let origin_location = match_expression!(self.next()?, AliasOrigin(origin), origin) .ok_or(AliasOriginExpected)?; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 3be500e98953..c58b6b9b5659 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -20,14 +20,12 @@ use crate::{ use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; -use rococo_westend_system_emulated_network::{ - bridge_hub_rococo_emulated_chain::genesis::ASSETHUB_PARA_ID, - penpal_emulated_chain::{ - penpal_runtime::xcm_config::{ - CheckingAccount, LocalTeleportableToAssetHub, TELEPORTABLE_ASSET_ID, - }, - PenpalAssetOwner, +use rococo_westend_system_emulated_network::penpal_emulated_chain::{ + penpal_runtime::xcm_config::{ + derived_from_here, AccountIdOf, CheckingAccount, LocalTeleportableToAssetHub, + TELEPORTABLE_ASSET_ID, }, + PenpalAssetOwner, }; use snowbridge_core::AssetMetadata; use snowbridge_outbound_primitives::TransactInfo; @@ -39,7 +37,7 @@ use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 50_000_000_000_000; const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); -const TOKEN_AMOUNT: u128 = 1_000_000_000_000; +const TOKEN_AMOUNT: u128 = 10_000_000_000_000; const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 400_000_000_000; const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 800_000_000_000; @@ -64,12 +62,11 @@ pub fn beneficiary() -> Location { } pub fn asset_hub() -> Location { - Location::new(1, Parachain(ASSETHUB_PARA_ID)) + Location::new(1, Parachain(AssetHubWestend::para_id().into())) } pub fn fund_on_bh() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(asset_hub()); BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); } @@ -96,21 +93,9 @@ pub fn register_weth_on_ah() { assert!(::ForeignAssets::asset_exists( weth_location().try_into().unwrap(), )); - - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendReceiver::get(), - TOKEN_AMOUNT, - )); - - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendSender::get(), - TOKEN_AMOUNT, - )); }); } -pub fn register_relay_token() { +pub fn register_relay_token_on_bh() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; type RuntimeOrigin = ::RuntimeOrigin; @@ -148,16 +133,6 @@ pub fn register_weth_on_penpal() { true, 1, )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &PenpalBReceiver::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &PenpalBSender::get(), - TOKEN_AMOUNT, - )); }); } @@ -194,10 +169,16 @@ pub fn register_pal_on_ah() { } pub fn fund_on_penpal() { + let sudo_account = derived_from_here::< + AccountIdOf< + rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::Runtime, + >, + >(); PenpalB::fund_accounts(vec![ (PenpalBReceiver::get(), INITIAL_FUND), (PenpalBSender::get(), INITIAL_FUND), (CheckingAccount::get(), INITIAL_FUND), + (sudo_account.clone(), INITIAL_FUND), ]); PenpalB::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( @@ -210,6 +191,11 @@ pub fn fund_on_penpal() { &PenpalBSender::get(), TOKEN_AMOUNT, )); + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &sudo_account, + TOKEN_AMOUNT, + )); }); PenpalB::execute_with(|| { assert_ok!(::Assets::mint_into( @@ -222,6 +208,28 @@ pub fn fund_on_penpal() { &PenpalBSender::get(), TOKEN_AMOUNT, )); + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &sudo_account, + TOKEN_AMOUNT, + )); + }); + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &sudo_account, + TOKEN_AMOUNT, + )); }); } @@ -238,6 +246,9 @@ pub fn set_trust_reserve_on_penpal() { } pub fn fund_on_ah() { + AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]); + let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalB::para_id()), ); @@ -248,6 +259,16 @@ pub fn fund_on_ah() { &penpal_sovereign, TOKEN_AMOUNT, )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + TOKEN_AMOUNT, + )); }); let ethereum_sovereign: AccountId = @@ -260,7 +281,7 @@ pub fn fund_on_ah() { AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); } -pub fn create_pools() { +pub fn create_pools_on_ah() { // We create a pool between WND and WETH in AssetHub to support paying for fees with WETH. let ethereum_sovereign: AccountId = EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( @@ -272,8 +293,6 @@ pub fn create_pools() { AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); create_pool_with_native_on!(AssetHubWestend, weth_location(), true, ethereum_sovereign.clone()); - // We also need a pool between WND and WETH on PenpalB to support paying for fees with WETH. - create_pool_with_native_on!(PenpalB, weth_location(), true, ethereum_sovereign.clone()); } pub fn register_pal_on_bh() { @@ -297,12 +316,84 @@ pub fn register_pal_on_bh() { }); } +fn register_ah_user_agent_on_ethereum() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { network: None, id: AssetHubWestendSender::get().into() }, + ], + ); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); +} + +pub fn register_penpal_agent_on_ethereum() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new(1, [Parachain(PenpalB::para_id().into())]); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new( + 1, + [ + Parachain(PenpalB::para_id().into()), + AccountId32 { network: None, id: PenpalBSender::get().into() }, + ], + ); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); +} + #[test] fn send_weth_from_asset_hub_to_ethereum() { fund_on_bh(); register_weth_on_ah(); + fund_on_ah(); + AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -361,7 +452,7 @@ fn send_weth_from_asset_hub_to_ethereum() { } #[test] -fn transfer_relay_token() { +fn transfer_relay_token_from_ah() { let ethereum_sovereign: AccountId = EthereumLocationsConverterFor::<[u8; 32]>::convert_location(ðereum()) .unwrap() @@ -369,9 +460,11 @@ fn transfer_relay_token() { fund_on_bh(); + register_relay_token_on_bh(); + register_weth_on_ah(); - register_relay_token(); + fund_on_ah(); // Send token to Ethereum AssetHubWestend::execute_with(|| { @@ -445,9 +538,11 @@ fn transfer_relay_token() { fn send_weth_and_dot_from_asset_hub_to_ethereum() { fund_on_bh(); + register_relay_token_on_bh(); + register_weth_on_ah(); - register_relay_token(); + fund_on_ah(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -506,33 +601,6 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { }); } -#[test] -fn create_agent() { - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - let location = Location::new( - 1, - [ - Parachain(AssetHubWestend::para_id().into()), - AccountId32 { network: None, id: AssetHubWestendSender::get().into() }, - ], - ); - - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] - ); - }); -} - #[test] fn transact_with_agent() { let weth_asset_location: Location = @@ -540,9 +608,11 @@ fn transact_with_agent() { fund_on_bh(); + register_ah_user_agent_on_ethereum(); + register_weth_on_ah(); - BridgeHubWestend::execute_with(|| {}); + fund_on_ah(); AssetHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -598,7 +668,7 @@ fn transact_with_agent() { ])); ::PolkadotXcm::execute( - RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + RuntimeOrigin::signed(AssetHubWestendSender::get()), bx!(xcms), Weight::from(EXECUTION_WEIGHT), ) @@ -615,23 +685,26 @@ fn transact_with_agent() { }); } -#[test] -fn send_penpal_native_asset_to_ethereum() { +fn send_message_from_penpal_to_ethereum(sudo: bool) { + // bh fund_on_bh(); + register_penpal_agent_on_ethereum(); + // ah register_weth_on_ah(); register_pal_on_ah(); register_pal_on_bh(); fund_on_ah(); - fund_on_penpal(); - register_weth_on_penpal(); + create_pools_on_ah(); + // penpal set_trust_reserve_on_penpal(); - create_pools(); + register_weth_on_penpal(); + fund_on_penpal(); PenpalB::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; let local_fee_asset_on_penpal = - Asset { id: AssetId(Location::parent()), fun: Fungible(TOKEN_AMOUNT) }; + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; let remote_fee_asset_on_ah = Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; @@ -639,9 +712,11 @@ fn send_penpal_native_asset_to_ethereum() { let remote_fee_asset_on_ethereum = Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; - let transfer_asset = + let pna = Asset { id: AssetId(LocalTeleportableToAssetHub::get()), fun: Fungible(TOKEN_AMOUNT) }; + let ena = Asset { id: AssetId(weth_location()), fun: Fungible(TOKEN_AMOUNT / 2) }; + let transfer_asset_reanchor_on_ah = Asset { id: AssetId(PenpalBTeleportableAssetLocation::get()), fun: Fungible(TOKEN_AMOUNT), @@ -651,13 +726,14 @@ fn send_penpal_native_asset_to_ethereum() { local_fee_asset_on_penpal.clone(), remote_fee_asset_on_ah.clone(), remote_fee_asset_on_ethereum.clone(), - transfer_asset.clone(), + pna.clone(), + ena.clone(), ]; let transact_info = TransactInfo { target: Default::default(), data: vec![], gas_limit: 40000, value: 0 }; - let xcms = VersionedXcm::from(Xcm(vec![ + let xcm = VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), PayFees { asset: local_fee_asset_on_penpal.clone() }, InitiateTransfer { @@ -670,10 +746,11 @@ fn send_penpal_native_asset_to_ethereum() { AssetTransferFilter::ReserveWithdraw(Definite( remote_fee_asset_on_ethereum.clone().into(), )), + AssetTransferFilter::ReserveWithdraw(Definite(ena.clone().into())), // Should use Teleport here because: // a. Penpal is configured to allow teleport specific asset to AH // b. AH is configured to trust asset teleport from sibling chain - AssetTransferFilter::Teleport(Definite(transfer_asset.clone().into())), + AssetTransferFilter::Teleport(Definite(pna.clone().into())), ], remote_xcm: Xcm(vec![InitiateTransfer { destination: ethereum(), @@ -681,11 +758,14 @@ fn send_penpal_native_asset_to_ethereum() { remote_fee_asset_on_ethereum.clone().into(), ))), preserve_origin: true, - // should use ReserveDeposit because Ethereum does not trust asset from penpal. - // transfer_asset should be reachored first on AH - assets: vec![AssetTransferFilter::ReserveDeposit(Definite( - transfer_asset_reanchor_on_ah.clone().into(), - ))], + assets: vec![ + // should use ReserveDeposit because Ethereum does not trust asset from + // penpal. transfer_asset should be reachored first on AH + AssetTransferFilter::ReserveDeposit(Definite( + transfer_asset_reanchor_on_ah.clone().into(), + )), + AssetTransferFilter::ReserveWithdraw(Definite(ena.clone().into())), + ], remote_xcm: Xcm(vec![ DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, Transact { @@ -697,11 +777,19 @@ fn send_penpal_native_asset_to_ethereum() { }, ])); - assert_ok!(::PolkadotXcm::execute( - RuntimeOrigin::signed(PenpalBSender::get()), - bx!(xcms), - Weight::from(EXECUTION_WEIGHT), - )); + if sudo { + assert_ok!(::PolkadotXcm::execute( + RuntimeOrigin::root(), + bx!(xcm.clone()), + Weight::from(EXECUTION_WEIGHT), + )); + } else { + assert_ok!(::PolkadotXcm::execute( + RuntimeOrigin::signed(PenpalBSender::get()), + bx!(xcm.clone()), + Weight::from(EXECUTION_WEIGHT), + )); + } }); AssetHubWestend::execute_with(|| { @@ -724,3 +812,13 @@ fn send_penpal_native_asset_to_ethereum() { ); }); } + +#[test] +fn send_message_from_penpal_to_ethereum_with_sudo() { + send_message_from_penpal_to_ethereum(true) +} + +#[test] +fn send_message_from_penpal_to_ethereum_with_user_origin() { + send_message_from_penpal_to_ethereum(false) +} diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 10481d5d2ebc..cb83994c0161 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -30,6 +30,7 @@ use super::{ }; use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee}; use assets_common::TrustBackedAssetsAsLocation; +use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{ parameter_types, @@ -45,7 +46,9 @@ use parachains_common::{xcm_config::AssetFeeAsExistentialDepositMultiplier, TREA use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::{impls::ToAuthor, xcm_sender::ExponentialPrice}; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; -use sp_runtime::traits::{AccountIdConversion, ConvertInto, Identity, TryConvertInto}; +use sp_runtime::traits::{ + AccountIdConversion, ConvertInto, Identity, TrailingZeroInput, TryConvertInto, +}; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ AccountId32Aliases, AliasOriginRootUsingFilter, AllowHrmpNotificationsFromRelayChain, @@ -59,7 +62,10 @@ use xcm_builder::{ SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; -use xcm_executor::{traits::JustTry, XcmExecutor}; +use xcm_executor::{ + traits::{ConvertLocation, JustTry}, + XcmExecutor, +}; parameter_types! { pub const RelayLocation: Location = Location::parent(); @@ -82,10 +88,34 @@ parameter_types! { PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); } +pub fn derived_from_here() -> AccountId +where + AccountId: Decode + Eq + Clone, +{ + b"Here" + .using_encoded(|b| AccountId::decode(&mut TrailingZeroInput::new(b))) + .expect("infinite length input; no invalid inputs for type; qed") +} + +/// A [`Location`] consisting of a single `Here` [`Junction`] will be converted to the +/// here `AccountId`. +pub struct HereIsPreset(PhantomData); +impl ConvertLocation for HereIsPreset { + fn convert_location(location: &Location) -> Option { + if location.contains_parents_only(0) { + Some(derived_from_here::()) + } else { + None + } + } +} + /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used /// when determining ownership of accounts for asset transacting and when attempting to use XCM /// `Transact` in order to determine the dispatch Origin. pub type LocationToAccountId = ( + // Here converts to `AccountId`. + HereIsPreset, // The parent (Relay-chain) origin converts to the parent `AccountId`. ParentIsPreset, // Sibling parachain origins convert to AccountId via the `ParaId::into`. From 27403eb01a67e324314e17b63f2783eabbff733a Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Dec 2024 21:11:53 +0800 Subject: [PATCH 38/81] Add BridgeHubDualMessageRouter --- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 - .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- .../runtimes/bridge-hubs/common/src/lib.rs | 4 ++- .../bridge-hubs/common/src/message_queue.rs | 33 +++++++++++++++++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index a090d1e9799c..1809d78cf0b6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -419,7 +419,6 @@ impl pallet_message_queue::Config for Runtime { RuntimeCall, >, EthereumOutboundQueue, - EthereumOutboundQueue, >; type Size = u32; // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index bf91526ab079..1d837efa7c13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -392,7 +392,7 @@ impl pallet_message_queue::Config for Runtime { type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; #[cfg(not(feature = "runtime-benchmarks"))] - type MessageProcessor = bridge_hub_common::BridgeHubMessageRouter< + type MessageProcessor = bridge_hub_common::BridgeHubDualMessageRouter< xcm_builder::ProcessXcmMessage< AggregateMessageOrigin, xcm_executor::XcmExecutor, diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs index b806b8cdb22d..13585ddf0840 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs @@ -19,4 +19,6 @@ pub mod message_queue; pub mod xcm_version; pub use digest_item::CustomDigestItem; -pub use message_queue::{AggregateMessageOrigin, BridgeHubMessageRouter}; +pub use message_queue::{ + AggregateMessageOrigin, BridgeHubDualMessageRouter, BridgeHubMessageRouter, +}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs index cdc4c741d863..1e6404fcd008 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs @@ -83,8 +83,37 @@ impl From for AggregateMessageOrigin { } } +pub struct BridgeHubMessageRouter( + PhantomData<(XcmpProcessor, SnowbridgeProcessor)>, +) +where + XcmpProcessor: ProcessMessage, + SnowbridgeProcessor: ProcessMessage; +impl ProcessMessage + for BridgeHubMessageRouter +where + XcmpProcessor: ProcessMessage, + SnowbridgeProcessor: ProcessMessage, +{ + type Origin = AggregateMessageOrigin; + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + use AggregateMessageOrigin::*; + match origin { + Here | Parent | Sibling(_) => + XcmpProcessor::process_message(message, origin, meter, id), + Snowbridge(_) => SnowbridgeProcessor::process_message(message, origin, meter, id), + SnowbridgeV2(_) => Err(ProcessMessageError::Unsupported), + } + } +} + /// Routes messages to either the XCMP or Snowbridge processor. -pub struct BridgeHubMessageRouter( +pub struct BridgeHubDualMessageRouter( PhantomData<(XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2)>, ) where @@ -92,7 +121,7 @@ where SnowbridgeProcessor: ProcessMessage; impl ProcessMessage - for BridgeHubMessageRouter + for BridgeHubDualMessageRouter where XcmpProcessor: ProcessMessage, SnowbridgeProcessor: ProcessMessage, From a0fff215c46a53e2d8015e8f08e65ae9d744c122 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 5 Dec 2024 21:42:00 +0800 Subject: [PATCH 39/81] PR doc --- prdoc/pr_6706.prdoc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 prdoc/pr_6706.prdoc diff --git a/prdoc/pr_6706.prdoc b/prdoc/pr_6706.prdoc new file mode 100644 index 000000000000..d04a2dceeea4 --- /dev/null +++ b/prdoc/pr_6706.prdoc @@ -0,0 +1,26 @@ +title: 'Snowbridge Unordered Message Delivery - Outbound Queue' +doc: +- audience: Node Dev + description: |- + New pallets for unordered message delivery for Snowbridge, specifically the Outbound Queue part. No breaking changes + are made in this PR, only new functionality added. + +crates: +- name: snowbridge-pallet-outbound-queue-v2 + bump: minor +- name: snowbridge-outbound-queue-runtime-api-v2 + bump: minor +- name: snowbridge-core + bump: major +- name: snowbridge-outbound-primitives + bump: major +- name: snowbridge-router-primitives + bump: major +- name: snowbridge-outbound-router-primitives + bump: major +- name: bridge-hub-westend-integration-tests + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: bridge-hub-rococo-runtime + bump: minor \ No newline at end of file From c76f8167272c987a2b00e39250e0c171435fc9d0 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 6 Dec 2024 18:00:39 +0800 Subject: [PATCH 40/81] Fix comments --- .../pallets/outbound-queue-v2/runtime-api/README.md | 2 +- .../pallets/outbound-queue-v2/src/send_message_impl.rs | 6 +----- bridges/snowbridge/primitives/outbound/README.md | 5 ++--- .../runtimes/bridge-hubs/common/src/message_queue.rs | 3 ++- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md index 98ae01fb33da..bd7930bbc527 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/README.md @@ -3,4 +3,4 @@ Provides an API: - to generate merkle proofs for outbound messages -- calculate delivery fee for delivering messages to Ethereum +- dry run the xcm to get a message structure to execute on Ethereum diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs index 6c9a34c3d53a..7b4285a52be9 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Implementation for [`snowbridge_core::outbound::SendMessage`] +//! Implementation for [`snowbridge_outbound_primitives::outbound::v2::SendMessage`] use super::*; use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; @@ -15,10 +15,6 @@ use snowbridge_outbound_primitives::{ use sp_core::H256; use sp_runtime::BoundedVec; -/// The maximal length of an enqueued message, as determined by the MessageQueue pallet -pub type MaxEnqueuedMessageSizeOf = - <::MessageQueue as EnqueueMessage>::MaxMessageLen; - impl SendMessage for Pallet where T: Config, diff --git a/bridges/snowbridge/primitives/outbound/README.md b/bridges/snowbridge/primitives/outbound/README.md index 0126be63aeba..c799fc386131 100644 --- a/bridges/snowbridge/primitives/outbound/README.md +++ b/bridges/snowbridge/primitives/outbound/README.md @@ -1,4 +1,3 @@ -# Core Primitives +# Outbound Primitives -Contains common code core to Snowbridge, such as inbound and outbound queue types, pricing structs, ringbuffer data -types (used in the beacon client). +Contains common code core for outbound queue types. diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs index 1e6404fcd008..3150bde9774e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs @@ -83,6 +83,7 @@ impl From for AggregateMessageOrigin { } } +/// Routes messages to either the XCMP or Snowbridge processor. pub struct BridgeHubMessageRouter( PhantomData<(XcmpProcessor, SnowbridgeProcessor)>, ) @@ -112,7 +113,7 @@ where } } -/// Routes messages to either the XCMP or Snowbridge processor. +/// Routes messages to either the XCMP|Snowbridge V1 processor|Snowbridge V2 processor pub struct BridgeHubDualMessageRouter( PhantomData<(XcmpProcessor, SnowbridgeProcessor, SnowbridgeProcessorV2)>, ) From bee50a993d335b9a8e6aa4eced62599345b86c34 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 6 Dec 2024 23:37:20 +0800 Subject: [PATCH 41/81] Check for universal source always from AH --- .../primitives/outbound-router/src/v2/mod.rs | 22 +++++++++++++++++-- .../outbound-router/src/v2/tests.rs | 21 ++++++++++++++++-- .../src/bridge_to_ethereum_config.rs | 3 +++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs index eeffc7361d34..782cbf18cffc 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/mod.rs @@ -13,7 +13,7 @@ use frame_support::{ ensure, traits::{Contains, Get, ProcessMessageError}, }; -use snowbridge_core::TokenId; +use snowbridge_core::{ParaId, TokenId}; use snowbridge_outbound_primitives::v2::SendMessage; use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; @@ -31,6 +31,7 @@ pub struct EthereumBlobExporter< AgentHashedDescription, ConvertAssetId, WETHAddress, + AssetHubParaId, >( PhantomData<( UniversalLocation, @@ -39,6 +40,7 @@ pub struct EthereumBlobExporter< AgentHashedDescription, ConvertAssetId, WETHAddress, + AssetHubParaId, )>, ); @@ -49,6 +51,7 @@ impl< AgentHashedDescription, ConvertAssetId, WETHAddress, + AssetHubParaId, > ExportXcm for EthereumBlobExporter< UniversalLocation, @@ -57,6 +60,7 @@ impl< AgentHashedDescription, ConvertAssetId, WETHAddress, + AssetHubParaId, > where UniversalLocation: Get, @@ -65,6 +69,7 @@ where AgentHashedDescription: ConvertLocation, ConvertAssetId: MaybeEquivalence, WETHAddress: Get, + AssetHubParaId: Get, { type Ticket = (Vec, XcmHash); @@ -93,7 +98,7 @@ where } // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. - let (local_net, _) = universal_source.clone() + let (local_net, local_sub) = universal_source.clone() .ok_or_else(|| { log::error!(target: TARGET, "universal source not provided."); SendError::MissingArgument @@ -109,6 +114,19 @@ where return Err(SendError::NotApplicable) } + let para_id = match local_sub.as_slice() { + [Parachain(para_id)] => *para_id, + _ => { + log::error!(target: TARGET, "could not get parachain id from universal source '{local_sub:?}'."); + return Err(SendError::NotApplicable) + }, + }; + + if ParaId::from(para_id) != AssetHubParaId::get() { + log::error!(target: TARGET, "is not from asset hub '{para_id:?}'."); + return Err(SendError::NotApplicable) + } + let message = message.clone().ok_or_else(|| { log::error!(target: TARGET, "xcm message not provided."); SendError::MissingArgument diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs index e5eaba48c179..6892f20c5963 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/tests.rs @@ -17,6 +17,7 @@ parameter_types! { pub const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; pub const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; pub WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); + pub AssetHubParaId: ParaId = ParaId::from(1000); } struct MockOkOutboundQueue; @@ -90,6 +91,7 @@ fn exporter_validate_with_unknown_network_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -110,6 +112,7 @@ fn exporter_validate_with_invalid_destination_yields_missing_argument() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -133,6 +136,7 @@ fn exporter_validate_with_x8_destination_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -153,6 +157,7 @@ fn exporter_validate_without_universal_source_yields_missing_argument() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -173,6 +178,7 @@ fn exporter_validate_without_global_universal_location_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -193,6 +199,7 @@ fn exporter_validate_without_global_bridge_location_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -214,6 +221,7 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); } @@ -234,8 +242,9 @@ fn exporter_validate_without_para_id_in_source_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); + assert_eq!(result, Err(XcmSendError::NotApplicable)); } #[test] @@ -255,8 +264,9 @@ fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); + assert_eq!(result, Err(XcmSendError::NotApplicable)); } #[test] @@ -276,6 +286,7 @@ fn exporter_validate_without_xcm_message_yields_missing_argument() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::MissingArgument)); } @@ -325,6 +336,7 @@ fn exporter_validate_with_max_target_fee_yields_unroutable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); @@ -353,6 +365,7 @@ fn exporter_validate_with_unparsable_xcm_yields_unroutable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert_eq!(result, Err(XcmSendError::NotApplicable)); @@ -405,6 +418,7 @@ fn exporter_validate_xcm_success_case_1() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); assert!(result.is_ok()); @@ -419,6 +433,7 @@ fn exporter_deliver_with_submit_failure_yields_unroutable() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) } @@ -464,6 +479,7 @@ fn exporter_validate_with_invalid_dest_does_not_alter_destination() { AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); @@ -518,6 +534,7 @@ fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_sour AgentIdOf, MockTokenIdConvert, WETHAddress, + AssetHubParaId, >::validate( network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index f1b800824fe2..422995e45638 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -42,6 +42,7 @@ use testnet_parachains_constants::westend::{ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; +use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -63,6 +64,7 @@ pub type SnowbridgeExporter = EthereumBlobExporter< parameter_types! { pub storage WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); + pub AssetHubParaId: ParaId = ParaId::from(westend_runtime_constants::system_parachain::ASSET_HUB_ID); } pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< @@ -72,6 +74,7 @@ pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< snowbridge_core::AgentIdOf, EthereumSystem, WETHAddress, + AssetHubParaId, >; // Ethereum Bridge From 194a5a962fe264b6e20ebabba6aa64fdd1e4bd86 Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 9 Dec 2024 21:27:32 +0800 Subject: [PATCH 42/81] Refactor simulated test with common codes --- .../bridge-hub-westend/src/tests/mod.rs | 1 + .../src/tests/snowbridge_common.rs | 373 ++++++++++++++++++ .../src/tests/snowbridge_v2_outbound.rs | 360 +---------------- 3 files changed, 381 insertions(+), 353 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 4c49614c6a96..4f2e7c77be21 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -20,6 +20,7 @@ mod claim_assets; mod register_bridged_assets; mod send_xcm; mod snowbridge; +mod snowbridge_common; mod snowbridge_v2_outbound; mod teleport; mod transact; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs new file mode 100644 index 000000000000..4ef0ffdf7f78 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -0,0 +1,373 @@ +use crate::{ + create_pool_with_native_on, + imports::*, + tests::snowbridge::{CHAIN_ID, WETH}, +}; +use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; +use frame_support::traits::fungibles::Mutate; +use hex_literal::hex; +use rococo_westend_system_emulated_network::penpal_emulated_chain::{ + penpal_runtime::xcm_config::{ + derived_from_here, AccountIdOf, CheckingAccount, LocalTeleportableToAssetHub, + TELEPORTABLE_ASSET_ID, + }, + PenpalAssetOwner, +}; +use snowbridge_core::AssetMetadata; +use snowbridge_outbound_primitives::TransactInfo; +use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; +use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +use xcm::v5::AssetTransferFilter; +use xcm_executor::traits::ConvertLocation; + +pub const INITIAL_FUND: u128 = 50_000_000_000_000; +pub const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); +pub const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); +pub const TOKEN_AMOUNT: u128 = 10_000_000_000_000; +pub const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 400_000_000_000; +pub const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 800_000_000_000; + +pub const EXECUTION_WEIGHT: u64 = 8_000_000_000; + +pub fn weth_location() -> Location { + Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { network: None, key: WETH }, + ], + ) +} + +pub fn ethereum() -> Location { + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]) +} + +pub fn beneficiary() -> Location { + Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) +} + +pub fn asset_hub() -> Location { + Location::new(1, Parachain(AssetHubWestend::para_id().into())) +} + +pub fn fund_on_bh() { + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(asset_hub()); + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn register_weth_on_ah() { + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + ethereum_sovereign.clone().into(), + true, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + }); +} +pub fn register_relay_token_on_bh() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Register WND on BH + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(Location::parent())), + AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + }); +} + +pub fn register_weth_on_penpal() { + PenpalB::execute_with(|| { + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + ethereum_sovereign.into(), + true, + 1, + )); + }); +} + +pub fn register_pal_on_ah() { + // Create PAL(i.e. native asset for penpal) on AH. + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + let penpal_asset_id = Location::new(1, Parachain(PenpalB::para_id().into())); + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + penpal_asset_id.clone(), + PenpalAssetOwner::get().into(), + false, + 1_000_000, + )); + + assert!(::ForeignAssets::asset_exists( + penpal_asset_id.clone(), + )); + + assert_ok!(::ForeignAssets::mint_into( + penpal_asset_id.clone(), + &AssetHubWestendReceiver::get(), + TOKEN_AMOUNT, + )); + + assert_ok!(::ForeignAssets::mint_into( + penpal_asset_id.clone(), + &AssetHubWestendSender::get(), + TOKEN_AMOUNT, + )); + }); +} + +pub fn fund_on_penpal() { + let sudo_account = derived_from_here::< + AccountIdOf< + rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::Runtime, + >, + >(); + PenpalB::fund_accounts(vec![ + (PenpalBReceiver::get(), INITIAL_FUND), + (PenpalBSender::get(), INITIAL_FUND), + (CheckingAccount::get(), INITIAL_FUND), + (sudo_account.clone(), INITIAL_FUND), + ]); + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + Location::parent(), + &sudo_account, + TOKEN_AMOUNT, + )); + }); + PenpalB::execute_with(|| { + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::Assets::mint_into( + TELEPORTABLE_ASSET_ID, + &sudo_account, + TOKEN_AMOUNT, + )); + }); + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &PenpalBSender::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &sudo_account, + TOKEN_AMOUNT, + )); + }); +} + +pub fn set_trust_reserve_on_penpal() { + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); +} + +pub fn fund_on_ah() { + AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]); + + let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + + AssetHubWestend::execute_with(|| { + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &penpal_sovereign, + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + TOKEN_AMOUNT, + )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + TOKEN_AMOUNT, + )); + }); + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); +} + +pub fn create_pools_on_ah() { + // We create a pool between WND and WETH in AssetHub to support paying for fees with WETH. + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( + 2, + [GlobalConsensus(EthereumNetwork::get())], + )) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + create_pool_with_native_on!(AssetHubWestend, weth_location(), true, ethereum_sovereign.clone()); +} + +pub fn register_pal_on_bh() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(PenpalBTeleportableAssetLocation::get())), + AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] + ); + }); +} + +pub fn register_ah_user_agent_on_ethereum() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { network: None, id: AssetHubWestendSender::get().into() }, + ], + ); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); +} + +pub fn register_penpal_agent_on_ethereum() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new(1, [Parachain(PenpalB::para_id().into())]); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let location = Location::new( + 1, + [ + Parachain(PenpalB::para_id().into()), + AccountId32 { network: None, id: PenpalBSender::get().into() }, + ], + ); + + assert_ok!( + ::EthereumSystem::force_create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + ) + ); + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index c58b6b9b5659..21d7781ad7ba 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -12,10 +12,14 @@ // 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::{ create_pool_with_native_on, imports::*, - tests::snowbridge::{CHAIN_ID, WETH}, + tests::{ + snowbridge::{CHAIN_ID, WETH}, + snowbridge_common::*, + }, }; use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; use frame_support::traits::fungibles::Mutate; @@ -34,358 +38,6 @@ use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 50_000_000_000_000; -const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); -const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); -const TOKEN_AMOUNT: u128 = 10_000_000_000_000; -const REMOTE_FEE_AMOUNT_IN_WETH: u128 = 400_000_000_000; -const LOCAL_FEE_AMOUNT_IN_DOT: u128 = 800_000_000_000; - -const EXECUTION_WEIGHT: u64 = 8_000_000_000; - -pub fn weth_location() -> Location { - Location::new( - 2, - [ - GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), - AccountKey20 { network: None, key: WETH }, - ], - ) -} - -pub fn ethereum() -> Location { - Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]) -} - -pub fn beneficiary() -> Location { - Location::new(0, [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }]) -} - -pub fn asset_hub() -> Location { - Location::new(1, Parachain(AssetHubWestend::para_id().into())) -} - -pub fn fund_on_bh() { - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(asset_hub()); - BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); -} - -pub fn register_weth_on_ah() { - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_location().try_into().unwrap(), - ethereum_sovereign.clone().into(), - true, - 1, - )); - - assert!(::ForeignAssets::asset_exists( - weth_location().try_into().unwrap(), - )); - }); -} -pub fn register_relay_token_on_bh() { - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - // Register WND on BH - assert_ok!(::EthereumSystem::register_token( - RuntimeOrigin::root(), - Box::new(VersionedLocation::from(Location::parent())), - AssetMetadata { - name: "wnd".as_bytes().to_vec().try_into().unwrap(), - symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), - decimals: 12, - }, - )); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] - ); - }); -} - -pub fn register_weth_on_penpal() { - PenpalB::execute_with(|| { - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - assert_ok!(::ForeignAssets::force_create( - ::RuntimeOrigin::root(), - weth_location().try_into().unwrap(), - ethereum_sovereign.into(), - true, - 1, - )); - }); -} - -pub fn register_pal_on_ah() { - // Create PAL(i.e. native asset for penpal) on AH. - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - let penpal_asset_id = Location::new(1, Parachain(PenpalB::para_id().into())); - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - penpal_asset_id.clone(), - PenpalAssetOwner::get().into(), - false, - 1_000_000, - )); - - assert!(::ForeignAssets::asset_exists( - penpal_asset_id.clone(), - )); - - assert_ok!(::ForeignAssets::mint_into( - penpal_asset_id.clone(), - &AssetHubWestendReceiver::get(), - TOKEN_AMOUNT, - )); - - assert_ok!(::ForeignAssets::mint_into( - penpal_asset_id.clone(), - &AssetHubWestendSender::get(), - TOKEN_AMOUNT, - )); - }); -} - -pub fn fund_on_penpal() { - let sudo_account = derived_from_here::< - AccountIdOf< - rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::Runtime, - >, - >(); - PenpalB::fund_accounts(vec![ - (PenpalBReceiver::get(), INITIAL_FUND), - (PenpalBSender::get(), INITIAL_FUND), - (CheckingAccount::get(), INITIAL_FUND), - (sudo_account.clone(), INITIAL_FUND), - ]); - PenpalB::execute_with(|| { - assert_ok!(::ForeignAssets::mint_into( - Location::parent(), - &PenpalBReceiver::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - Location::parent(), - &PenpalBSender::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - Location::parent(), - &sudo_account, - TOKEN_AMOUNT, - )); - }); - PenpalB::execute_with(|| { - assert_ok!(::Assets::mint_into( - TELEPORTABLE_ASSET_ID, - &PenpalBReceiver::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::Assets::mint_into( - TELEPORTABLE_ASSET_ID, - &PenpalBSender::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::Assets::mint_into( - TELEPORTABLE_ASSET_ID, - &sudo_account, - TOKEN_AMOUNT, - )); - }); - PenpalB::execute_with(|| { - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &PenpalBReceiver::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &PenpalBSender::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &sudo_account, - TOKEN_AMOUNT, - )); - }); -} - -pub fn set_trust_reserve_on_penpal() { - PenpalB::execute_with(|| { - assert_ok!(::System::set_storage( - ::RuntimeOrigin::root(), - vec![( - PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), - Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), - )], - )); - }); -} - -pub fn fund_on_ah() { - AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get(), INITIAL_FUND)]); - AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]); - - let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( - AssetHubWestend::sibling_location_of(PenpalB::para_id()), - ); - - AssetHubWestend::execute_with(|| { - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &penpal_sovereign, - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendReceiver::get(), - TOKEN_AMOUNT, - )); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendSender::get(), - TOKEN_AMOUNT, - )); - }); - - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); -} - -pub fn create_pools_on_ah() { - // We create a pool between WND and WETH in AssetHub to support paying for fees with WETH. - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&Location::new( - 2, - [GlobalConsensus(EthereumNetwork::get())], - )) - .unwrap() - .into(); - AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - PenpalB::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - create_pool_with_native_on!(AssetHubWestend, weth_location(), true, ethereum_sovereign.clone()); -} - -pub fn register_pal_on_bh() { - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - assert_ok!(::EthereumSystem::register_token( - RuntimeOrigin::root(), - Box::new(VersionedLocation::from(PenpalBTeleportableAssetLocation::get())), - AssetMetadata { - name: "pal".as_bytes().to_vec().try_into().unwrap(), - symbol: "pal".as_bytes().to_vec().try_into().unwrap(), - decimals: 12, - }, - )); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::RegisterToken { .. }) => {},] - ); - }); -} - -fn register_ah_user_agent_on_ethereum() { - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - let location = Location::new( - 1, - [ - Parachain(AssetHubWestend::para_id().into()), - AccountId32 { network: None, id: AssetHubWestendSender::get().into() }, - ], - ); - - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] - ); - }); -} - -pub fn register_penpal_agent_on_ethereum() { - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - let location = Location::new(1, [Parachain(PenpalB::para_id().into())]); - - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] - ); - }); - - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - type RuntimeOrigin = ::RuntimeOrigin; - - let location = Location::new( - 1, - [ - Parachain(PenpalB::para_id().into()), - AccountId32 { network: None, id: PenpalBSender::get().into() }, - ], - ); - - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] - ); - }); -} - #[test] fn send_weth_from_asset_hub_to_ethereum() { fund_on_bh(); @@ -661,6 +313,7 @@ fn transact_with_agent() { DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, Transact { origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, call: transact_info.encode().into(), }, ]), @@ -770,6 +423,7 @@ fn send_message_from_penpal_to_ethereum(sudo: bool) { DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, Transact { origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, call: transact_info.encode().into(), }, ]), From 7da30490fd2515e73ee220feb80db6409c898cbc Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 00:11:25 +0800 Subject: [PATCH 43/81] Seperate simulated tests for edge cases --- .../bridge-hub-westend/src/tests/mod.rs | 2 + .../src/tests/snowbridge_common.rs | 33 +++---- .../src/tests/snowbridge_edge_case.rs | 95 +++++++++++++++++++ .../src/tests/snowbridge_v2_outbound.rs | 17 +--- 4 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 4f2e7c77be21..b66b8661e9a6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -25,6 +25,8 @@ mod snowbridge_v2_outbound; mod teleport; mod transact; +mod snowbridge_edge_case; + pub(crate) fn asset_hub_rococo_location() -> Location { Location::new( 2, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index 4ef0ffdf7f78..2d12b7fe7ee4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -8,16 +8,13 @@ use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use rococo_westend_system_emulated_network::penpal_emulated_chain::{ penpal_runtime::xcm_config::{ - derived_from_here, AccountIdOf, CheckingAccount, LocalTeleportableToAssetHub, - TELEPORTABLE_ASSET_ID, + derived_from_here, AccountIdOf, CheckingAccount, TELEPORTABLE_ASSET_ID, }, PenpalAssetOwner, }; use snowbridge_core::AssetMetadata; -use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; -use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; pub const INITIAL_FUND: u128 = 50_000_000_000_000; @@ -51,6 +48,10 @@ pub fn asset_hub() -> Location { Location::new(1, Parachain(AssetHubWestend::para_id().into())) } +pub fn bridge_hub() -> Location { + Location::new(1, Parachain(BridgeHubWestend::para_id().into())) +} + pub fn fund_on_bh() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(asset_hub()); BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); @@ -170,51 +171,51 @@ pub fn fund_on_penpal() { assert_ok!(::ForeignAssets::mint_into( Location::parent(), &PenpalBReceiver::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( Location::parent(), &PenpalBSender::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( Location::parent(), &sudo_account, - TOKEN_AMOUNT, + INITIAL_FUND, )); }); PenpalB::execute_with(|| { assert_ok!(::Assets::mint_into( TELEPORTABLE_ASSET_ID, &PenpalBReceiver::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::Assets::mint_into( TELEPORTABLE_ASSET_ID, &PenpalBSender::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::Assets::mint_into( TELEPORTABLE_ASSET_ID, &sudo_account, - TOKEN_AMOUNT, + INITIAL_FUND, )); }); PenpalB::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &PenpalBReceiver::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &PenpalBSender::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &sudo_account, - TOKEN_AMOUNT, + INITIAL_FUND, )); }); } @@ -243,17 +244,17 @@ pub fn fund_on_ah() { assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &penpal_sovereign, - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &AssetHubWestendReceiver::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &AssetHubWestendSender::get(), - TOKEN_AMOUNT, + INITIAL_FUND, )); }); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs new file mode 100644 index 000000000000..fa857b639917 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -0,0 +1,95 @@ +// Copyright (C) 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::{ + imports::*, + tests::{ + snowbridge::{CHAIN_ID, WETH}, + snowbridge_common::*, + }, +}; +use bridge_hub_westend_runtime::xcm_config::LocationToAccountId; +use xcm_executor::traits::ConvertLocation; + +// The user origin should be banned in ethereum_blob_exporter with error logs +// xcm::ethereum_blob_exporter: could not get parachain id from universal source +// 'X2([Parachain(1000), AccountId32 {...}])' +#[test] +fn user_export_message_from_ah_directly_will_fail() { + fund_on_bh(); + register_weth_on_ah(); + fund_on_ah(); + create_pools_on_ah(); + + let sov_account_for_sender = LocationToAccountId::convert_location(&Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + AccountId32 { + network: Some(ByGenesis(WESTEND_GENESIS_HASH)), + id: AssetHubWestendSender::get().into(), + }, + ], + )) + .unwrap(); + BridgeHubWestend::fund_accounts(vec![(sov_account_for_sender, INITIAL_FUND)]); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_asset = + Asset { id: AssetId(Location::parent()), fun: Fungible(1_000_000_000_000) }; + + let weth_location_reanchored = + Location::new(0, [AccountKey20 { network: None, key: WETH.into() }]); + + let weth_asset = Asset { + id: AssetId(weth_location_reanchored.clone()), + fun: Fungible(TOKEN_AMOUNT * 1_000_000_000), + }; + + assert_ok!(::PolkadotXcm::send( + RuntimeOrigin::signed(AssetHubWestendSender::get()), + bx!(VersionedLocation::from(bridge_hub())), + bx!(VersionedXcm::from(Xcm(vec![ + WithdrawAsset(local_fee_asset.clone().into()), + BuyExecution { fees: local_fee_asset.clone(), weight_limit: Unlimited }, + ExportMessage { + network: Ethereum { chain_id: CHAIN_ID }, + destination: Here, + xcm: Xcm(vec![ + WithdrawAsset(weth_asset.clone().into()), + DepositAsset { assets: Wild(All), beneficiary: beneficiary() }, + SetTopic([0; 32]), + ]), + }, + ]))), + )); + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent{ .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed{ success:false, .. }) => {},] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 21d7781ad7ba..4df72dc4c5a0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -14,24 +14,11 @@ // limitations under the License. use crate::{ - create_pool_with_native_on, imports::*, - tests::{ - snowbridge::{CHAIN_ID, WETH}, - snowbridge_common::*, - }, + tests::{snowbridge::WETH, snowbridge_common::*}, }; use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; -use frame_support::traits::fungibles::Mutate; -use hex_literal::hex; -use rococo_westend_system_emulated_network::penpal_emulated_chain::{ - penpal_runtime::xcm_config::{ - derived_from_here, AccountIdOf, CheckingAccount, LocalTeleportableToAssetHub, - TELEPORTABLE_ASSET_ID, - }, - PenpalAssetOwner, -}; -use snowbridge_core::AssetMetadata; +use rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::LocalTeleportableToAssetHub; use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; From 00c44359149cb4433300fe8df71064caeb0f20b5 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 00:39:16 +0800 Subject: [PATCH 44/81] Add test register ena on bh will fail --- .../src/tests/snowbridge_edge_case.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs index fa857b639917..a1a904ee8b09 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_edge_case.rs @@ -21,6 +21,8 @@ use crate::{ }, }; use bridge_hub_westend_runtime::xcm_config::LocationToAccountId; +use snowbridge_core::AssetMetadata; +use snowbridge_pallet_system::Error; use xcm_executor::traits::ConvertLocation; // The user origin should be banned in ethereum_blob_exporter with error logs @@ -93,3 +95,31 @@ fn user_export_message_from_ah_directly_will_fail() { ); }); } + +// ENA is not allowed to be registered as PNA +#[test] +fn test_register_ena_on_bh_will_fail() { + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + + assert_ok!(::Balances::force_set_balance( + RuntimeOrigin::root(), + sp_runtime::MultiAddress::Id(BridgeHubWestendSender::get()), + INITIAL_FUND * 10, + )); + + assert_err!( + ::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(weth_location())), + AssetMetadata { + name: "weth".as_bytes().to_vec().try_into().unwrap(), + symbol: "weth".as_bytes().to_vec().try_into().unwrap(), + decimals: 18, + }, + ), + Error::::LocationConversionFailed + ); + }); +} From b678286fde43ec2109f7de979e42c4782cee4c53 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 10:52:09 +0800 Subject: [PATCH 45/81] Add test print configurable keys --- .../bridge-hub-westend/tests/snowbridge.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index 1a1ce2a28ea3..2f590eef25d7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -20,16 +20,19 @@ use bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID; use bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID; use bp_polkadot_core::Signature; use bridge_hub_westend_runtime::{ - bridge_to_rococo_config, xcm_config::XcmConfig, AllPalletsWithoutSystem, - BridgeRejectObsoleteHeadersAndMessages, Executive, MessageQueueServiceWeight, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, TxExtension, UncheckedExtrinsic, + bridge_to_ethereum_config::{EthereumGatewayAddress, WETHAddress}, + bridge_to_rococo_config, + xcm_config::XcmConfig, + AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, + MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, TxExtension, + UncheckedExtrinsic, }; use codec::{Decode, Encode}; use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; -use sp_core::H160; +use sp_core::{bytes::to_hex, H160}; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, @@ -200,3 +203,11 @@ fn construct_and_apply_extrinsic( let r = Executive::apply_extrinsic(xt); r.unwrap() } + +#[test] +fn snowbridge_configurable_key() { + let weth_key = WETHAddress::key().to_vec(); + assert_eq!(to_hex(weth_key.as_slice(), true), "0x36f2f46ef8ffc0cc013470f259488ca1"); + let gateway_key = EthereumGatewayAddress::key().to_vec(); + assert_eq!(to_hex(gateway_key.as_slice(), true), "0xaed97c7854d601808b98ae43079dafb3"); +} From 88e8e61813c3eb95e5e5c2433fdb49a223ac40d3 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 11:09:45 +0800 Subject: [PATCH 46/81] Fix umbrella --- Cargo.lock | 5 +++++ umbrella/Cargo.toml | 32 ++++++++++++++++++++++++++++++++ umbrella/src/lib.rs | 20 ++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1a11e3994bac..327dbfa1ac59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18735,12 +18735,17 @@ dependencies = [ "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", "snowbridge-ethereum 0.3.0", + "snowbridge-merkle-tree", + "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api 0.2.0", + "snowbridge-outbound-queue-runtime-api-v2", + "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-ethereum-client-fixtures 0.9.0", "snowbridge-pallet-inbound-queue 0.2.0", "snowbridge-pallet-inbound-queue-fixtures 0.10.0", "snowbridge-pallet-outbound-queue 0.2.0", + "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system 0.2.0", "snowbridge-router-primitives 0.9.0", "snowbridge-runtime-common 0.2.0", diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 14af5ebbea72..78198e757e22 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -168,11 +168,16 @@ std = [ "snowbridge-beacon-primitives?/std", "snowbridge-core?/std", "snowbridge-ethereum?/std", + "snowbridge-merkle-tree?/std", + "snowbridge-outbound-primitives?/std", + "snowbridge-outbound-queue-runtime-api-v2?/std", "snowbridge-outbound-queue-runtime-api?/std", + "snowbridge-outbound-router-primitives?/std", "snowbridge-pallet-ethereum-client-fixtures?/std", "snowbridge-pallet-ethereum-client?/std", "snowbridge-pallet-inbound-queue-fixtures?/std", "snowbridge-pallet-inbound-queue?/std", + "snowbridge-pallet-outbound-queue-v2?/std", "snowbridge-pallet-outbound-queue?/std", "snowbridge-pallet-system?/std", "snowbridge-router-primitives?/std", @@ -351,6 +356,7 @@ runtime-benchmarks = [ "snowbridge-pallet-ethereum-client?/runtime-benchmarks", "snowbridge-pallet-inbound-queue-fixtures?/runtime-benchmarks", "snowbridge-pallet-inbound-queue?/runtime-benchmarks", + "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-outbound-queue?/runtime-benchmarks", "snowbridge-pallet-system?/runtime-benchmarks", "snowbridge-router-primitives?/runtime-benchmarks", @@ -477,6 +483,7 @@ try-runtime = [ "polkadot-service?/try-runtime", "snowbridge-pallet-ethereum-client?/try-runtime", "snowbridge-pallet-inbound-queue?/try-runtime", + "snowbridge-pallet-outbound-queue-v2?/try-runtime", "snowbridge-pallet-outbound-queue?/try-runtime", "snowbridge-pallet-system?/try-runtime", "sp-runtime?/try-runtime", @@ -1485,6 +1492,31 @@ path = "../bridges/snowbridge/pallets/system/runtime-api" default-features = false optional = true +[dependencies.snowbridge-merkle-tree] +path = "../bridges/snowbridge/primitives/merkle-tree" +default-features = false +optional = true + +[dependencies.snowbridge-outbound-primitives] +path = "../bridges/snowbridge/primitives/outbound" +default-features = false +optional = true + +[dependencies.snowbridge-outbound-router-primitives] +path = "../bridges/snowbridge/primitives/outbound-router" +default-features = false +optional = true + +[dependencies.snowbridge-pallet-outbound-queue-v2] +path = "../bridges/snowbridge/pallets/outbound-queue-v2" +default-features = false +optional = true + +[dependencies.snowbridge-outbound-queue-runtime-api-v2] +path = "../bridges/snowbridge/pallets/outbound-queue-v2/runtime-api" +default-features = false +optional = true + [dependencies.sp-api] path = "../substrate/primitives/api" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index a2b70cb68210..036c1781c343 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -1228,6 +1228,26 @@ pub use snowbridge_runtime_test_common; #[cfg(feature = "snowbridge-system-runtime-api")] pub use snowbridge_system_runtime_api; +/// Snowbridge merkle tree. +#[cfg(feature = "snowbridge-merkle-tree")] +pub use snowbridge_merkle_tree; + +/// Snowbridge Outbound primitives. +#[cfg(feature = "snowbridge-outbound-primitives")] +pub use snowbridge_outbound_primitives; + +/// Snowbridge Outbound router primitives. +#[cfg(feature = "snowbridge-outbound-router-primitives")] +pub use snowbridge_outbound_router_primitives; + +/// Snowbridge Outbound Queue Runtime API. +#[cfg(feature = "snowbridge-outbound-queue-runtime-api-v2")] +pub use snowbridge_outbound_queue_runtime_api_v2; + +/// Snowbridge Outbound Queue Pallet V2. +#[cfg(feature = "snowbridge-pallet-outbound-queue-v2")] +pub use snowbridge_pallet_outbound_queue_v2; + /// Substrate runtime api primitives. #[cfg(feature = "sp-api")] pub use sp_api; From a4297093d2397c53546c908fb0eb53c1b2565bf1 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 11:11:29 +0800 Subject: [PATCH 47/81] Fix doc --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 4 ++-- .../pallets/outbound-queue-v2/src/send_message_impl.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 3fdc838e3039..e1cd08a20efe 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -12,9 +12,9 @@ //! //! The message submission pipeline works like this: //! 1. The message is first validated via the implementation for -//! [`snowbridge_core::outbound::v2::SendMessage::validate`] +//! [`snowbridge_outbound_primitives::v2::SendMessage::validate`] //! 2. The message is then enqueued for later processing via the implementation for -//! [`snowbridge_core::outbound::v2::SendMessage::deliver`] +//! [`snowbridge_outbound_primitives::v2::SendMessage::deliver`] //! 3. The underlying message queue is implemented by [`Config::MessageQueue`] //! 4. The message queue delivers messages back to this pallet via the implementation for //! [`frame_support::traits::ProcessMessage::process_message`] diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs index 7b4285a52be9..c254ce44d8eb 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/send_message_impl.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Implementation for [`snowbridge_outbound_primitives::outbound::v2::SendMessage`] +//! Implementation for [`snowbridge_outbound_primitives::v2::SendMessage`] use super::*; use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; From 81a0d8b081b32d81ae02c223684fda229566dd7a Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 19:55:13 +0800 Subject: [PATCH 48/81] Add SystemV2 pallet --- Cargo.lock | 39 +++ Cargo.toml | 4 + .../snowbridge/pallets/system-v2/Cargo.toml | 83 ++++++ .../snowbridge/pallets/system-v2/README.md | 3 + .../pallets/system-v2/runtime-api/Cargo.toml | 34 +++ .../pallets/system-v2/runtime-api/README.md | 3 + .../pallets/system-v2/runtime-api/src/lib.rs | 13 + .../snowbridge/pallets/system-v2/src/api.rs | 16 ++ .../pallets/system-v2/src/benchmarking.rs | 52 ++++ .../snowbridge/pallets/system-v2/src/lib.rs | 257 ++++++++++++++++++ .../snowbridge/pallets/system-v2/src/mock.rs | 226 +++++++++++++++ .../snowbridge/pallets/system-v2/src/tests.rs | 60 ++++ .../pallets/system-v2/src/weights.rs | 73 +++++ .../bridge-hubs/bridge-hub-westend/Cargo.toml | 6 + .../src/bridge_to_ethereum_config.rs | 40 ++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 10 +- .../bridge-hub-westend/src/weights/mod.rs | 1 + .../weights/snowbridge_pallet_system_v2.rs | 82 ++++++ 18 files changed, 998 insertions(+), 4 deletions(-) create mode 100644 bridges/snowbridge/pallets/system-v2/Cargo.toml create mode 100644 bridges/snowbridge/pallets/system-v2/README.md create mode 100644 bridges/snowbridge/pallets/system-v2/runtime-api/Cargo.toml create mode 100644 bridges/snowbridge/pallets/system-v2/runtime-api/README.md create mode 100644 bridges/snowbridge/pallets/system-v2/runtime-api/src/lib.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/api.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/benchmarking.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/lib.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/mock.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/tests.rs create mode 100644 bridges/snowbridge/pallets/system-v2/src/weights.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system_v2.rs diff --git a/Cargo.lock b/Cargo.lock index 4589c245d5a4..5b641910d546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2847,10 +2847,12 @@ dependencies = [ "snowbridge-pallet-outbound-queue 0.2.0", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system 0.2.0", + "snowbridge-pallet-system-v2", "snowbridge-router-primitives 0.9.0", "snowbridge-runtime-common 0.2.0", "snowbridge-runtime-test-common 0.2.0", "snowbridge-system-runtime-api 0.2.0", + "snowbridge-system-runtime-api-v2", "sp-api 26.0.0", "sp-block-builder 26.0.0", "sp-consensus-aura 0.32.0", @@ -25238,6 +25240,32 @@ dependencies = [ "staging-xcm-executor 17.0.0", ] +[[package]] +name = "snowbridge-pallet-system-v2" +version = "0.2.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "hex", + "hex-literal", + "log", + "pallet-balances 28.0.0", + "pallet-xcm 7.0.0", + "parity-scale-codec", + "polkadot-primitives 7.0.0", + "scale-info", + "snowbridge-core 0.2.0", + "snowbridge-outbound-primitives", + "snowbridge-pallet-outbound-queue-v2", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", +] + [[package]] name = "snowbridge-router-primitives" version = "0.9.0" @@ -25395,6 +25423,17 @@ dependencies = [ "staging-xcm 14.2.0", ] +[[package]] +name = "snowbridge-system-runtime-api-v2" +version = "0.2.0" +dependencies = [ + "parity-scale-codec", + "snowbridge-core 0.2.0", + "sp-api 26.0.0", + "sp-std 14.0.0", + "staging-xcm 7.0.0", +] + [[package]] name = "socket2" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 96f73a05d394..b19eab307091 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,8 @@ members = [ "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/outbound-queue/runtime-api", "bridges/snowbridge/pallets/system", + "bridges/snowbridge/pallets/system-v2", + "bridges/snowbridge/pallets/system-v2/runtime-api", "bridges/snowbridge/pallets/system/runtime-api", "bridges/snowbridge/primitives/beacon", "bridges/snowbridge/primitives/core", @@ -1239,10 +1241,12 @@ snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/palle snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } snowbridge-pallet-outbound-queue-v2 = { path = "bridges/snowbridge/pallets/outbound-queue-v2", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } +snowbridge-pallet-system-v2 = { path = "bridges/snowbridge/pallets/system-v2", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } snowbridge-runtime-common = { path = "bridges/snowbridge/runtime/runtime-common", default-features = false } snowbridge-runtime-test-common = { path = "bridges/snowbridge/runtime/test-common", default-features = false } snowbridge-system-runtime-api = { path = "bridges/snowbridge/pallets/system/runtime-api", default-features = false } +snowbridge-system-runtime-api-v2 = { path = "bridges/snowbridge/pallets/system-v2/runtime-api", default-features = false } soketto = { version = "0.8.0" } solochain-template-runtime = { path = "templates/solochain/runtime" } sp-api = { path = "substrate/primitives/api", default-features = false } diff --git a/bridges/snowbridge/pallets/system-v2/Cargo.toml b/bridges/snowbridge/pallets/system-v2/Cargo.toml new file mode 100644 index 000000000000..854b0b6d7c03 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "snowbridge-pallet-system-v2" +description = "Snowbridge System Pallet V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = [ + "derive", +], workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } + +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } + +xcm = { workspace = true } +xcm-executor = { workspace = true } +pallet-xcm = { workspace = true } + +snowbridge-core = { workspace = true } +snowbridge-outbound-primitives = { workspace = true } + +[dev-dependencies] +hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +snowbridge-pallet-outbound-queue-v2 = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-xcm/std", + "scale-info/std", + "snowbridge-core/std", + "snowbridge-outbound-primitives/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "snowbridge-pallet-outbound-queue-v2/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/system-v2/README.md b/bridges/snowbridge/pallets/system-v2/README.md new file mode 100644 index 000000000000..5ab11d45eae2 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/README.md @@ -0,0 +1,3 @@ +# Ethereum System + +Contains management functions to manage functions on Ethereum. For example, creating agents and channels. diff --git a/bridges/snowbridge/pallets/system-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system-v2/runtime-api/Cargo.toml new file mode 100644 index 000000000000..bfcffb816ed8 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/runtime-api/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snowbridge-system-runtime-api-v2" +description = "Snowbridge System Runtime API V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = [ + "derive", +], workspace = true } +sp-std = { workspace = true } +sp-api = { workspace = true } +xcm = { workspace = true } +snowbridge-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "snowbridge-core/std", + "sp-api/std", + "sp-std/std", + "xcm/std", +] diff --git a/bridges/snowbridge/pallets/system-v2/runtime-api/README.md b/bridges/snowbridge/pallets/system-v2/runtime-api/README.md new file mode 100644 index 000000000000..d7e81c9e7861 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/runtime-api/README.md @@ -0,0 +1,3 @@ +# Ethereum System Runtime API V2 + +Provides an API for looking up an agent ID on Ethereum. diff --git a/bridges/snowbridge/pallets/system-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/system-v2/runtime-api/src/lib.rs new file mode 100644 index 000000000000..c8cb777fff57 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/runtime-api/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_core::AgentId; +use xcm::VersionedLocation; + +sp_api::decl_runtime_apis! { + pub trait ControlV2Api + { + fn agent_id(location: VersionedLocation) -> Option; + } +} diff --git a/bridges/snowbridge/pallets/system-v2/src/api.rs b/bridges/snowbridge/pallets/system-v2/src/api.rs new file mode 100644 index 000000000000..ef12b03e1d75 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/api.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helpers for implementing runtime api + +use snowbridge_core::AgentId; +use xcm::{prelude::*, VersionedLocation}; + +use crate::{agent_id_of, Config}; + +pub fn agent_id(location: VersionedLocation) -> Option +where + Runtime: Config, +{ + let location: Location = location.try_into().ok()?; + agent_id_of::(&location).ok() +} diff --git a/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs new file mode 100644 index 000000000000..387103986355 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/benchmarking.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Benchmarking setup for pallet-template +use super::*; + +#[allow(unused)] +use crate::Pallet as SnowbridgeControl; +use frame_benchmarking::v2::*; +use xcm::prelude::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_agent() -> Result<(), BenchmarkError> { + let origin_location = Location::new(1, [Parachain(1000)]); + let origin = T::Helper::make_xcm_origin(origin_location); + + let agent_origin = Box::new(VersionedLocation::from(Location::parent())); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, agent_origin, 100); + + Ok(()) + } + + #[benchmark] + fn register_token() -> Result<(), BenchmarkError> { + let origin_location = Location::new(1, [Parachain(1000)]); + let origin = T::Helper::make_xcm_origin(origin_location); + + let relay_token_asset_id: Location = Location::parent(); + let asset = Box::new(VersionedLocation::from(relay_token_asset_id)); + let asset_metadata = AssetMetadata { + name: "wnd".as_bytes().to_vec().try_into().unwrap(), + symbol: "wnd".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, asset, asset_metadata, 100); + + Ok(()) + } + + impl_benchmark_test_suite!( + SnowbridgeControl, + crate::mock::new_test_ext(true), + crate::mock::Test + ); +} diff --git a/bridges/snowbridge/pallets/system-v2/src/lib.rs b/bridges/snowbridge/pallets/system-v2/src/lib.rs new file mode 100644 index 000000000000..a83dc63b3749 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/lib.rs @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Governance API for controlling the Ethereum side of the bridge +//! +//! # Extrinsics +//! +//! ## Agents +//! +//! Agents are smart contracts on Ethereum that act as proxies for consensus systems on Polkadot +//! networks. +//! +//! * [`Call::create_agent`]: Create agent for a sibling parachain +//! ## Polkadot-native tokens on Ethereum +//! +//! Tokens deposited on AssetHub pallet can be bridged to Ethereum as wrapped ERC20 tokens. As a +//! prerequisite, the token should be registered first. +//! +//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum. +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod api; +pub mod weights; +pub use weights::*; + +use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; +use frame_system::pallet_prelude::*; +use snowbridge_core::{AgentId, AssetMetadata, TokenId, TokenIdOf}; +use snowbridge_outbound_primitives::{ + v2::{Command, Message, SendMessage}, + SendError, +}; +use sp_core::H256; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::prelude::*; +use xcm::prelude::*; +use xcm_executor::traits::ConvertLocation; + +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::OriginTrait; + +pub use pallet::*; + +pub type AccountIdOf = ::AccountId; + +pub fn agent_id_of(location: &Location) -> Result { + T::AgentIdOf::convert_location(location).ok_or(Error::::LocationConversionFailed.into()) +} + +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper +where + O: OriginTrait, +{ + fn make_xcm_origin(location: Location) -> O; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Send messages to Ethereum + type OutboundQueue: SendMessage; + + /// Origin check for XCM locations that can create agents + type SiblingOrigin: EnsureOrigin; + + /// Converts Location to AgentId + type AgentIdOf: ConvertLocation; + + type WeightInfo: WeightInfo; + + /// This chain's Universal Location. + type UniversalLocation: Get; + + // The bridges configured Ethereum location + type EthereumLocation: Get; + + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An CreateAgent message was sent to the Gateway + CreateAgent { location: Box, agent_id: AgentId }, + /// Register Polkadot-native token as a wrapped ERC20 token on Ethereum + RegisterToken { + /// Location of Polkadot-native token + location: VersionedLocation, + /// ID of Polkadot-native token on Ethereum + foreign_token_id: H256, + }, + } + + #[pallet::error] + pub enum Error { + LocationConversionFailed, + AgentAlreadyCreated, + NoAgent, + UnsupportedLocationVersion, + InvalidLocation, + Send(SendError), + } + + /// The set of registered agents + #[pallet::storage] + #[pallet::getter(fn agents)] + pub type Agents = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>; + + /// Lookup table for foreign token ID to native location relative to ethereum + #[pallet::storage] + pub type ForeignToNativeId = + StorageMap<_, Blake2_128Concat, TokenId, xcm::v5::Location, OptionQuery>; + + /// Lookup table for native location relative to ethereum to foreign token ID + #[pallet::storage] + pub type NativeToForeignId = + StorageMap<_, Blake2_128Concat, xcm::v5::Location, TokenId, OptionQuery>; + + #[pallet::call] + impl Pallet { + /// Sends a command to the Gateway contract to instantiate a new agent contract representing + /// `origin`. + /// + /// Fee required: Yes + /// + /// - `origin`: Must be `Location` of a sibling parachain + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn create_agent( + origin: OriginFor, + location: Box, + fee: u128, + ) -> DispatchResult { + T::SiblingOrigin::ensure_origin(origin)?; + + let origin_location: Location = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + let agent_id = agent_id_of::(&origin_location)?; + + // Record the agent id or fail if it has already been created + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + + let command = Command::CreateAgent {}; + + Self::send(origin_location.clone(), command, fee)?; + + Self::deposit_event(Event::::CreateAgent { + location: Box::new(origin_location), + agent_id, + }); + Ok(()) + } + + /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `location`: Location of the asset (relative to this chain) + /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::register_token())] + pub fn register_token( + origin: OriginFor, + asset_id: Box, + metadata: AssetMetadata, + fee: u128, + ) -> DispatchResult { + let origin_location = T::SiblingOrigin::ensure_origin(origin)?; + + let asset_location: Location = + (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + let ethereum_location = T::EthereumLocation::get(); + // reanchor to Ethereum context + let location = asset_location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let token_id = TokenIdOf::convert_location(&location) + .ok_or(Error::::LocationConversionFailed)?; + + if !ForeignToNativeId::::contains_key(token_id) { + NativeToForeignId::::insert(location.clone(), token_id); + ForeignToNativeId::::insert(token_id, location.clone()); + } + + let command = Command::RegisterForeignToken { + token_id, + name: metadata.name.into_inner(), + symbol: metadata.symbol.into_inner(), + decimals: metadata.decimals, + }; + Self::send(origin_location, command, fee)?; + + Self::deposit_event(Event::::RegisterToken { + location: location.clone().into(), + foreign_token_id: token_id, + }); + + Ok(()) + } + } + + impl Pallet { + /// Send `command` to the Gateway on the Channel identified by `channel_id` + fn send(origin_location: Location, command: Command, fee: u128) -> DispatchResult { + let origin = agent_id_of::(&origin_location)?; + + let mut message = Message { + origin_location, + origin, + id: Default::default(), + fee, + commands: BoundedVec::try_from(vec![command]).unwrap(), + }; + let hash = sp_io::hashing::blake2_256(&message.encode()); + message.id = hash.into(); + + let (ticket, _) = + T::OutboundQueue::validate(&message).map_err(|err| Error::::Send(err))?; + + T::OutboundQueue::deliver(ticket).map_err(|err| Error::::Send(err))?; + Ok(()) + } + } + + impl MaybeEquivalence for Pallet { + fn convert(foreign_id: &TokenId) -> Option { + ForeignToNativeId::::get(foreign_id) + } + fn convert_back(location: &Location) -> Option { + NativeToForeignId::::get(location) + } + } +} diff --git a/bridges/snowbridge/pallets/system-v2/src/mock.rs b/bridges/snowbridge/pallets/system-v2/src/mock.rs new file mode 100644 index 000000000000..e985e0d878d0 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/mock.rs @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as snowbridge_system; +use frame_support::{ + derive_impl, parameter_types, + traits::{tokens::fungible::Mutate, ConstU128, Contains}, +}; +use sp_core::H256; +use xcm_executor::traits::ConvertLocation; + +use snowbridge_core::{sibling_sovereign_account, AgentId, ParaId}; +use snowbridge_outbound_primitives::{ + v2::{Message, SendMessage}, + SendMessageFeeProvider, +}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BuildStorage, +}; +use xcm::prelude::*; + +use crate::mock::pallet_xcm_origin::EnsureXcm; +#[cfg(feature = "runtime-benchmarks")] +use crate::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; +type Balance = u128; + +pub type AccountId = AccountId32; + +// A stripped-down version of pallet-xcm that only inserts an XCM origin into the runtime +#[allow(dead_code)] +#[frame_support::pallet] +mod pallet_xcm_origin { + use frame_support::{ + pallet_prelude::*, + traits::{Contains, OriginTrait}, + }; + use xcm::latest::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeOrigin: From + From<::RuntimeOrigin>; + } + + // Insert this custom Origin into the aggregate RuntimeOrigin + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct Origin(pub Location); + + impl From for Origin { + fn from(location: Location) -> Origin { + Origin(location) + } + } + + /// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and + /// filter the contained location + pub struct EnsureXcm(PhantomData); + impl, F: Contains> EnsureOrigin for EnsureXcm + where + O::PalletsOrigin: From + TryInto, + { + type Success = Location; + + fn try_origin(outer: O) -> Result { + outer.try_with_caller(|caller| { + caller.try_into().and_then(|o| match o { + Origin(location) if F::contains(&location) => Ok(location), + o => Err(o.into()), + }) + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin(Location::new(1, [Parachain(2000)])))) + } + } +} + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + XcmOrigin: pallet_xcm_origin::{Pallet, Origin}, + EthereumSystem: snowbridge_system, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type Nonce = u64; + type Block = Block; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; +} + +impl pallet_xcm_origin::Config for Test { + type RuntimeOrigin = RuntimeOrigin; +} + +pub struct MockOkOutboundQueue; +impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + type Balance = u128; + + fn validate( + _: &Message, + ) -> Result<(Self::Ticket, Self::Balance), snowbridge_outbound_primitives::SendError> { + Ok(((), 1_u128)) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + +parameter_types! { + pub const AnyNetwork: Option = None; + pub const RelayNetwork: Option = Some(Polkadot); + pub const RelayLocation: Location = Location::parent(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)].into(); + pub EthereumNetwork: NetworkId = Ethereum { chain_id: 11155111 }; + pub EthereumDestination: Location = Location::new(2,[GlobalConsensus(EthereumNetwork::get())]); +} + +parameter_types! { + pub Fee: u64 = 1000; + pub const InitialFunding: u128 = 1_000_000_000_000; + pub BridgeHubParaId: ParaId = ParaId::new(1002); + pub AssetHubParaId: ParaId = ParaId::new(1000); + pub TestParaId: u32 = 2000; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm_origin::Origin(location)) + } +} + +pub struct AllowFromAssetHub; +impl Contains for AllowFromAssetHub { + fn contains(location: &Location) -> bool { + match location.unpack() { + (1, [Parachain(para_id)]) => + if *para_id == 1000 { + true + } else { + false + }, + _ => false, + } + } +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = MockOkOutboundQueue; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type WeightInfo = (); + type UniversalLocation = UniversalLocation; + type EthereumLocation = EthereumDestination; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext(_genesis_build: bool) -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let mut ext: sp_io::TestExternalities = storage.into(); + let initial_amount = InitialFunding::get(); + let test_para_id = TestParaId::get(); + let sovereign_account = sibling_sovereign_account::(test_para_id.into()); + ext.execute_with(|| { + System::set_block_number(1); + Balances::mint_into(&AccountId32::from([0; 32]), initial_amount).unwrap(); + Balances::mint_into(&sovereign_account, initial_amount).unwrap(); + }); + ext +} + +// Test helpers + +pub fn make_xcm_origin(location: Location) -> RuntimeOrigin { + pallet_xcm_origin::Origin(location).into() +} + +pub fn make_agent_id(location: Location) -> AgentId { + ::AgentIdOf::convert_location(&location) + .expect("convert location") +} diff --git a/bridges/snowbridge/pallets/system-v2/src/tests.rs b/bridges/snowbridge/pallets/system-v2/src/tests.rs new file mode 100644 index 000000000000..996a963fa396 --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/tests.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{mock::*, *}; +use frame_support::{assert_noop, assert_ok}; +use sp_runtime::DispatchError::BadOrigin; + +#[test] +fn create_agent() { + new_test_ext(true).execute_with(|| { + let origin_location = Location::new(1, [Parachain(1000)]); + let origin = make_xcm_origin(origin_location); + + let agent_origin = Location::new(1, [Parachain(2000)]); + let agent_id = make_agent_id(agent_origin.clone()); + + assert!(!Agents::::contains_key(agent_id)); + assert_ok!(EthereumSystem::create_agent( + origin, + Box::new(VersionedLocation::from(agent_origin)), + 1 + )); + + assert!(Agents::::contains_key(agent_id)); + }); +} + +#[test] +fn create_agent_bad_origin() { + new_test_ext(true).execute_with(|| { + assert_noop!( + EthereumSystem::create_agent( + make_xcm_origin(Location::new(1, []),), + Box::new(Here.into()), + 1, + ), + BadOrigin, + ); + + // None origin not allowed + assert_noop!( + EthereumSystem::create_agent(RuntimeOrigin::none(), Box::new(Here.into()), 1), + BadOrigin + ); + }); +} + +#[test] +fn register_tokens_succeeds() { + new_test_ext(true).execute_with(|| { + let origin = make_xcm_origin(Location::new(1, [Parachain(1000)])); + let versioned_location: VersionedLocation = Location::parent().into(); + + assert_ok!(EthereumSystem::register_token( + origin, + Box::new(versioned_location), + Default::default(), + 1 + )); + }); +} diff --git a/bridges/snowbridge/pallets/system-v2/src/weights.rs b/bridges/snowbridge/pallets/system-v2/src/weights.rs new file mode 100644 index 000000000000..95a72623ebcf --- /dev/null +++ b/bridges/snowbridge/pallets/system-v2/src/weights.rs @@ -0,0 +1,73 @@ + +//! Autogenerated weights for `snowbridge_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `crake.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// bridge-hub-rococo-dev +// --pallet=snowbridge_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template +// ../parachain/templates/module-weight-template.hbs +// --output +// ../parachain/pallets/control/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `snowbridge_system`. +pub trait WeightInfo { + fn create_agent() -> Weight; + fn register_token() -> Weight; +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: EthereumSystem Agents (r:1 w:1) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn create_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `187` + // Estimated: `6196` + // Minimum execution time: 85_000_000 picoseconds. + Weight::from_parts(85_000_000, 6196) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 8a39a69fc72a..e05fd0d1aaab 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -109,6 +109,8 @@ bridge-hub-common = { workspace = true } snowbridge-beacon-primitives = { workspace = true } snowbridge-pallet-system = { workspace = true } snowbridge-system-runtime-api = { workspace = true } +snowbridge-pallet-system-v2 = { workspace = true } +snowbridge-system-runtime-api-v2 = { workspace = true } snowbridge-core = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } @@ -202,9 +204,11 @@ std = [ "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue-v2/std", "snowbridge-pallet-outbound-queue/std", + "snowbridge-pallet-system-v2/std", "snowbridge-pallet-system/std", "snowbridge-router-primitives/std", "snowbridge-runtime-common/std", + "snowbridge-system-runtime-api-v2/std", "snowbridge-system-runtime-api/std", "sp-api/std", "sp-block-builder/std", @@ -264,6 +268,7 @@ runtime-benchmarks = [ "snowbridge-pallet-inbound-queue/runtime-benchmarks", "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", "snowbridge-pallet-outbound-queue/runtime-benchmarks", + "snowbridge-pallet-system-v2/runtime-benchmarks", "snowbridge-pallet-system/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", "snowbridge-runtime-common/runtime-benchmarks", @@ -305,6 +310,7 @@ try-runtime = [ "snowbridge-pallet-inbound-queue/try-runtime", "snowbridge-pallet-outbound-queue-v2/try-runtime", "snowbridge-pallet-outbound-queue/try-runtime", + "snowbridge-pallet-system-v2/try-runtime", "snowbridge-pallet-system/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 422995e45638..f04ea80eaba5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -19,8 +19,8 @@ use crate::XcmRouter; use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, - Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, - RuntimeEvent, TransactionByteFee, + Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumOutboundQueueV2, EthereumSystem, + MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; @@ -43,7 +43,7 @@ use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; use cumulus_primitives_core::ParaId; -use frame_support::{parameter_types, weights::ConstantMultiplier}; +use frame_support::{parameter_types, traits::Contains, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ traits::{ConstU32, ConstU8, Keccak256}, @@ -232,6 +232,34 @@ impl snowbridge_pallet_system::Config for Runtime { type EthereumLocation = EthereumLocation; } +pub struct AllowFromAssetHub; +impl Contains for AllowFromAssetHub { + fn contains(location: &Location) -> bool { + match location.unpack() { + (1, [Parachain(para_id)]) => { + if *para_id == westend_runtime_constants::system_parachain::ASSET_HUB_ID { + true + } else { + false + } + }, + _ => false, + } + } +} + +impl snowbridge_pallet_system_v2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = EthereumOutboundQueueV2; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type WeightInfo = crate::weights::snowbridge_pallet_system_v2::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type UniversalLocation = UniversalLocation; + type EthereumLocation = EthereumLocation; +} + #[cfg(feature = "runtime-benchmarks")] pub mod benchmark_helpers { use crate::{EthereumBeaconClient, Runtime, RuntimeOrigin}; @@ -268,6 +296,12 @@ pub mod benchmark_helpers { RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) } } + + impl snowbridge_pallet_system_v2::BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } + } } pub(crate) mod migrations { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 1d837efa7c13..397cb0785b96 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -566,7 +566,8 @@ construct_runtime!( EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, EthereumSystem: snowbridge_pallet_system = 83, - EthereumOutboundQueueV2: snowbridge_pallet_outbound_queue_v2 = 85, + EthereumOutboundQueueV2: snowbridge_pallet_outbound_queue_v2 = 84, + EthereumSystemV2: snowbridge_pallet_system_v2 = 85, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -624,6 +625,7 @@ mod benches { [snowbridge_pallet_inbound_queue, EthereumInboundQueue] [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] + [snowbridge_pallet_system_v2, EthereumSystemV2] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] [snowbridge_pallet_outbound_queue_v2, EthereumOutboundQueueV2] ); @@ -920,6 +922,12 @@ impl_runtime_apis! { } } + impl snowbridge_system_runtime_api_v2::ControlV2Api for Runtime { + fn agent_id(location: VersionedLocation) -> Option { + snowbridge_pallet_system_v2::api::agent_id::(location) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index 27746c287933..961da1ca1a62 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -50,6 +50,7 @@ pub mod snowbridge_pallet_inbound_queue; pub mod snowbridge_pallet_outbound_queue; pub mod snowbridge_pallet_outbound_queue_v2; pub mod snowbridge_pallet_system; +pub mod snowbridge_pallet_system_v2; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system_v2.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system_v2.rs new file mode 100644 index 000000000000..7fbba5cda259 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system_v2.rs @@ -0,0 +1,82 @@ +// Copyright (C) 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. + +//! Autogenerated weights for `snowbridge_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `crake.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// bridge-hub-rococo-dev +// --pallet=snowbridge_pallet_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `snowbridge_system`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_system_v2::WeightInfo for WeightInfo { + /// Storage: EthereumSystem Agents (r:1 w:1) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn create_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `187` + // Estimated: `6196` + // Minimum execution time: 87_000_000 picoseconds. + Weight::from_parts(87_000_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) + } + + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } +} From caaacad17b74633b5fe1cb62492c47dd729adb53 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Dec 2024 23:49:29 +0800 Subject: [PATCH 49/81] Add System Frontend pallet --- Cargo.lock | 23 ++ Cargo.toml | 2 + .../pallets/system-frontend/Cargo.toml | 74 +++++++ .../pallets/system-frontend/README.md | 3 + .../system-frontend/src/benchmarking.rs | 51 +++++ .../pallets/system-frontend/src/lib.rs | 202 ++++++++++++++++++ .../pallets/system-frontend/src/mock.rs | 193 +++++++++++++++++ .../pallets/system-frontend/src/tests.rs | 54 +++++ .../pallets/system-frontend/src/weights.rs | 73 +++++++ .../snowbridge/pallets/system-v2/Cargo.toml | 1 + .../snowbridge/pallets/system-v2/src/lib.rs | 4 +- .../snowbridge/pallets/system-v2/src/mock.rs | 2 +- .../snowbridge/pallets/system-v2/src/tests.rs | 1 - 13 files changed, 679 insertions(+), 4 deletions(-) create mode 100644 bridges/snowbridge/pallets/system-frontend/Cargo.toml create mode 100644 bridges/snowbridge/pallets/system-frontend/README.md create mode 100644 bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs create mode 100644 bridges/snowbridge/pallets/system-frontend/src/lib.rs create mode 100644 bridges/snowbridge/pallets/system-frontend/src/mock.rs create mode 100644 bridges/snowbridge/pallets/system-frontend/src/tests.rs create mode 100644 bridges/snowbridge/pallets/system-frontend/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 5b641910d546..b659a4a00afe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25399,6 +25399,29 @@ dependencies = [ "staging-xcm-executor 17.0.0", ] +[[package]] +name = "snowbridge-system-frontend" +version = "0.2.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "hex", + "hex-literal", + "log", + "pallet-balances 28.0.0", + "pallet-xcm 7.0.0", + "parity-scale-codec", + "scale-info", + "snowbridge-core 0.2.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", +] + [[package]] name = "snowbridge-system-runtime-api" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index b19eab307091..1deb3e8ea058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "bridges/snowbridge/pallets/outbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/outbound-queue/runtime-api", "bridges/snowbridge/pallets/system", + "bridges/snowbridge/pallets/system-frontend", "bridges/snowbridge/pallets/system-v2", "bridges/snowbridge/pallets/system-v2/runtime-api", "bridges/snowbridge/pallets/system/runtime-api", @@ -1245,6 +1246,7 @@ snowbridge-pallet-system-v2 = { path = "bridges/snowbridge/pallets/system-v2", d snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } snowbridge-runtime-common = { path = "bridges/snowbridge/runtime/runtime-common", default-features = false } snowbridge-runtime-test-common = { path = "bridges/snowbridge/runtime/test-common", default-features = false } +snowbridge-system-frontend = { path = "bridges/snowbridge/pallets/system-frontend", default-features = false } snowbridge-system-runtime-api = { path = "bridges/snowbridge/pallets/system/runtime-api", default-features = false } snowbridge-system-runtime-api-v2 = { path = "bridges/snowbridge/pallets/system-v2/runtime-api", default-features = false } soketto = { version = "0.8.0" } diff --git a/bridges/snowbridge/pallets/system-frontend/Cargo.toml b/bridges/snowbridge/pallets/system-frontend/Cargo.toml new file mode 100644 index 000000000000..218bb661afd2 --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "snowbridge-system-frontend" +description = "Snowbridge System Frontend Pallet" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = [ + "derive", +], workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } +pallet-xcm = { workspace = true } +snowbridge-core = { workspace = true } + +[dev-dependencies] +hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-xcm/std", + "scale-info/std", + "snowbridge-core/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-xcm/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/system-frontend/README.md b/bridges/snowbridge/pallets/system-frontend/README.md new file mode 100644 index 000000000000..28d9ffb9a4e7 --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/README.md @@ -0,0 +1,3 @@ +# Ethereum System Frontend + +An frontend pallet deployed on AH for calling V2 system pallet on BH. diff --git a/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs new file mode 100644 index 000000000000..bc07844b68e7 --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/src/benchmarking.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Benchmarking setup for pallet-template +use super::*; + +#[allow(unused)] +use crate::Pallet as SnowbridgeControlFrontend; +use frame_benchmarking::v2::*; +use xcm::prelude::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_agent() -> Result<(), BenchmarkError> { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = T::Helper::make_xcm_origin(origin_location); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, 100); + + Ok(()) + } + + #[benchmark] + fn register_token() -> Result<(), BenchmarkError> { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = T::Helper::make_xcm_origin(origin_location); + + let asset_location: Location = Location::new(1, [Parachain(2000), GeneralIndex(1)]); + let asset_id = Box::new(VersionedLocation::from(asset_location)); + + let asset_metadata = AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, asset_id, asset_metadata, 100); + + Ok(()) + } + + impl_benchmark_test_suite!( + SnowbridgeControlFrontend, + crate::mock::new_test_ext(), + crate::mock::Test + ); +} diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs new file mode 100644 index 000000000000..ef09057fcba2 --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Frontend for calling Snowbridge System Pallet on BridgeHub +//! +//! # Extrinsics +//! +//! * [`Call::create_agent`]: Create agent for any sovereign location from non-system parachain +//! * [`Call::register_token`]: Register a foreign token location from non-system parachain +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; +pub use weights::*; + +use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; +use frame_system::pallet_prelude::*; +use sp_std::prelude::*; +use xcm::prelude::*; +use xcm_executor::traits::TransactAsset; + +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::OriginTrait; + +pub use pallet::*; +use snowbridge_core::AssetMetadata; + +#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] +pub enum ControlCall { + #[codec(index = 1)] + CreateAgent { location: Box, fee: u128 }, + #[codec(index = 2)] + RegisterToken { asset_id: Box, metadata: AssetMetadata, fee: u128 }, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] +pub enum SnowbridgeControl { + #[codec(index = 85)] + Control(ControlCall), +} + +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper +where + O: OriginTrait, +{ + fn make_xcm_origin(location: Location) -> O; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Origin check for XCM locations that can create agents + type CreateAgentOrigin: EnsureOrigin; + + /// Origin check for XCM locations that can create agents + type RegisterTokenOrigin: EnsureOrigin; + + /// XCM message sender + type XcmSender: SendXcm; + + /// To withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An CreateAgent message was sent to BH + CreateAgent { location: Location }, + /// Register Polkadot-native token was sent to BH + RegisterToken { + /// Location of Polkadot-native token + location: Location, + }, + } + + #[pallet::error] + pub enum Error { + UnsupportedLocationVersion, + OwnerCheck, + Send, + FundsUnavailable, + } + + #[pallet::call] + impl Pallet { + /// Call create_agent on BH to instantiate a new agent contract representing `origin`. + /// - `origin`: Must be `Location` from a sibling parachain + /// - `fee`: Fee in Ether + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn create_agent(origin: OriginFor, fee: u128) -> DispatchResult { + let origin_location = T::CreateAgentOrigin::ensure_origin(origin)?; + + Self::burn_weth(origin_location.clone(), fee)?; + + let call = SnowbridgeControl::Control(ControlCall::CreateAgent { + location: Box::new(VersionedLocation::from(origin_location.clone())), + fee, + }); + + let xcm: Xcm<()> = vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + call: call.encode().into(), + fallback_max_weight: None, + }, + ] + .into(); + + Self::send(xcm)?; + + Self::deposit_event(Event::::CreateAgent { location: origin_location.clone() }); + Ok(()) + } + + /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. + /// - `origin`: Must be `Location` from a sibling parachain + /// - `asset_id`: Location of the asset (should be starts from the dispatch origin) + /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::register_token())] + pub fn register_token( + origin: OriginFor, + asset_id: Box, + metadata: AssetMetadata, + fee: u128, + ) -> DispatchResult { + let origin_location = T::RegisterTokenOrigin::ensure_origin(origin)?; + + let asset_location: Location = + (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + + let mut checked = false; + if asset_location.eq(&origin_location) || asset_location.starts_with(&origin_location) { + checked = true + } + ensure!(checked, >::OwnerCheck); + + Self::burn_weth(origin_location.clone(), fee)?; + + let call = SnowbridgeControl::Control(ControlCall::RegisterToken { + asset_id: Box::new(VersionedLocation::from(asset_location.clone())), + metadata, + fee, + }); + + let xcm: Xcm<()> = vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + call: call.encode().into(), + fallback_max_weight: None, + }, + ] + .into(); + + Self::send(xcm)?; + + Self::deposit_event(Event::::RegisterToken { location: asset_location }); + + Ok(()) + } + } + + impl Pallet { + pub fn send(xcm: Xcm<()>) -> DispatchResult { + let bridgehub = Location::new(1, [Parachain(1002)]); + send_xcm::(bridgehub, xcm).map_err(|_| Error::::Send)?; + Ok(()) + } + pub fn burn_weth(origin_location: Location, fee: u128) -> DispatchResult { + let fee_asset = + (Location::new(2, [GlobalConsensus(Ethereum { chain_id: 1 })]), fee).into(); + T::AssetTransactor::withdraw_asset(&fee_asset, &origin_location, None) + .map_err(|_| Error::::FundsUnavailable)?; + Ok(()) + } + } +} diff --git a/bridges/snowbridge/pallets/system-frontend/src/mock.rs b/bridges/snowbridge/pallets/system-frontend/src/mock.rs new file mode 100644 index 000000000000..774a43d51560 --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/src/mock.rs @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as snowbridge_system_frontend; +use crate::mock::pallet_xcm_origin::EnsureXcm; +use codec::Encode; +use frame_support::{derive_impl, traits::Everything}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BuildStorage, +}; +use xcm::prelude::*; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; + +#[cfg(feature = "runtime-benchmarks")] +use crate::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; +type AccountId = AccountId32; + +// A stripped-down version of pallet-xcm that only inserts an XCM origin into the runtime +#[frame_support::pallet] +mod pallet_xcm_origin { + use frame_support::{ + pallet_prelude::*, + traits::{Contains, OriginTrait}, + }; + use xcm::latest::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeOrigin: From + From<::RuntimeOrigin>; + } + + // Insert this custom Origin into the aggregate RuntimeOrigin + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct Origin(pub Location); + + impl From for Origin { + fn from(location: Location) -> Origin { + Origin(location) + } + } + + /// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and + /// filter the contained location + pub struct EnsureXcm(PhantomData); + impl, F: Contains> EnsureOrigin for EnsureXcm + where + O::PalletsOrigin: From + TryInto, + { + type Success = Location; + + fn try_origin(outer: O) -> Result { + outer.try_with_caller(|caller| { + caller.try_into().and_then(|o| match o { + Origin(location) if F::contains(&location) => Ok(location), + o => Err(o.into()), + }) + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin(Location::new(1, [Parachain(2000)])))) + } + } +} + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + XcmOrigin: pallet_xcm_origin::{Pallet, Origin}, + EthereumSystemFrontend: snowbridge_system_frontend, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type Nonce = u64; + type Block = Block; +} + +impl pallet_xcm_origin::Config for Test { + type RuntimeOrigin = RuntimeOrigin; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm_origin::Origin(location)) + } +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; + +impl SendXcm for MockXcmSender { + type Ticket = Xcm<()>; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(SendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::default())) + } + } + + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } +} + +pub struct SuccessfulTransactor; +impl TransactAsset for SuccessfulTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateAgentOrigin = EnsureXcm; + type RegisterTokenOrigin = EnsureXcm; + type XcmSender = MockXcmSender; + type AssetTransactor = SuccessfulTransactor; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub fn make_xcm_origin(location: Location) -> RuntimeOrigin { + pallet_xcm_origin::Origin(location).into() +} diff --git a/bridges/snowbridge/pallets/system-frontend/src/tests.rs b/bridges/snowbridge/pallets/system-frontend/src/tests.rs new file mode 100644 index 000000000000..c948fd34661f --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/src/tests.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{mock::*, Error}; +use frame_support::{assert_noop, assert_ok}; +use snowbridge_core::AssetMetadata; +use xcm::{ + latest::Location, + prelude::{GeneralIndex, Parachain}, + VersionedLocation, +}; + +#[test] +fn create_agent() { + new_test_ext().execute_with(|| { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = make_xcm_origin(origin_location); + assert_ok!(EthereumSystemFrontend::create_agent(origin, 100)); + }); +} + +#[test] +fn register_token() { + new_test_ext().execute_with(|| { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = make_xcm_origin(origin_location); + let asset_location: Location = Location::new(1, [Parachain(2000), GeneralIndex(1)]); + let asset_id = Box::new(VersionedLocation::from(asset_location)); + let asset_metadata = AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + assert_ok!(EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, 100)); + }); +} + +#[test] +fn register_token_fail_for_owner_check() { + new_test_ext().execute_with(|| { + let origin_location = Location::new(1, [Parachain(2000)]); + let origin = make_xcm_origin(origin_location); + let asset_location: Location = Location::new(1, [Parachain(2001), GeneralIndex(1)]); + let asset_id = Box::new(VersionedLocation::from(asset_location)); + let asset_metadata = AssetMetadata { + name: "pal".as_bytes().to_vec().try_into().unwrap(), + symbol: "pal".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }; + assert_noop!( + EthereumSystemFrontend::register_token(origin, asset_id, asset_metadata, 100), + Error::::OwnerCheck + ); + }); +} diff --git a/bridges/snowbridge/pallets/system-frontend/src/weights.rs b/bridges/snowbridge/pallets/system-frontend/src/weights.rs new file mode 100644 index 000000000000..95a72623ebcf --- /dev/null +++ b/bridges/snowbridge/pallets/system-frontend/src/weights.rs @@ -0,0 +1,73 @@ + +//! Autogenerated weights for `snowbridge_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `crake.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// bridge-hub-rococo-dev +// --pallet=snowbridge_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template +// ../parachain/templates/module-weight-template.hbs +// --output +// ../parachain/pallets/control/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `snowbridge_system`. +pub trait WeightInfo { + fn create_agent() -> Weight; + fn register_token() -> Weight; +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: EthereumSystem Agents (r:1 w:1) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn create_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `187` + // Estimated: `6196` + // Minimum execution time: 85_000_000 picoseconds. + Weight::from_parts(85_000_000, 6196) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + fn register_token() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } +} diff --git a/bridges/snowbridge/pallets/system-v2/Cargo.toml b/bridges/snowbridge/pallets/system-v2/Cargo.toml index 854b0b6d7c03..2cc55148a6e4 100644 --- a/bridges/snowbridge/pallets/system-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/system-v2/Cargo.toml @@ -78,6 +78,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-xcm/try-runtime", "snowbridge-pallet-outbound-queue-v2/try-runtime", "sp-runtime/try-runtime", ] diff --git a/bridges/snowbridge/pallets/system-v2/src/lib.rs b/bridges/snowbridge/pallets/system-v2/src/lib.rs index a83dc63b3749..c94793d2c890 100644 --- a/bridges/snowbridge/pallets/system-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/system-v2/src/lib.rs @@ -141,7 +141,7 @@ pub mod pallet { /// Fee required: Yes /// /// - `origin`: Must be `Location` of a sibling parachain - #[pallet::call_index(3)] + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::create_agent())] pub fn create_agent( origin: OriginFor, @@ -178,7 +178,7 @@ pub mod pallet { /// - `origin`: Must be root /// - `location`: Location of the asset (relative to this chain) /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum - #[pallet::call_index(10)] + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::register_token())] pub fn register_token( origin: OriginFor, diff --git a/bridges/snowbridge/pallets/system-v2/src/mock.rs b/bridges/snowbridge/pallets/system-v2/src/mock.rs index e985e0d878d0..be2390bb8539 100644 --- a/bridges/snowbridge/pallets/system-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/system-v2/src/mock.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -use crate as snowbridge_system; use frame_support::{ derive_impl, parameter_types, traits::{tokens::fungible::Mutate, ConstU128, Contains}, @@ -8,6 +7,7 @@ use frame_support::{ use sp_core::H256; use xcm_executor::traits::ConvertLocation; +use crate as snowbridge_system; use snowbridge_core::{sibling_sovereign_account, AgentId, ParaId}; use snowbridge_outbound_primitives::{ v2::{Message, SendMessage}, diff --git a/bridges/snowbridge/pallets/system-v2/src/tests.rs b/bridges/snowbridge/pallets/system-v2/src/tests.rs index 996a963fa396..69575f04f12a 100644 --- a/bridges/snowbridge/pallets/system-v2/src/tests.rs +++ b/bridges/snowbridge/pallets/system-v2/src/tests.rs @@ -3,7 +3,6 @@ use crate::{mock::*, *}; use frame_support::{assert_noop, assert_ok}; use sp_runtime::DispatchError::BadOrigin; - #[test] fn create_agent() { new_test_ext(true).execute_with(|| { From be0a0ee97af322aa91fde673b04f62bdbb366df4 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 14:03:12 +0800 Subject: [PATCH 50/81] Fix regenerate umbrella --- .gitignore | 1 + umbrella/Cargo.toml | 55 ++++++++-------- umbrella/src/lib.rs | 151 +++++++++++++++++--------------------------- 3 files changed, 86 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index d48287657085..d5e0f1875da7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ substrate.code-workspace target/ *.scale justfile +python-venv diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 78198e757e22..a72af30e2abe 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -356,7 +356,7 @@ runtime-benchmarks = [ "snowbridge-pallet-ethereum-client?/runtime-benchmarks", "snowbridge-pallet-inbound-queue-fixtures?/runtime-benchmarks", "snowbridge-pallet-inbound-queue?/runtime-benchmarks", - "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", + "snowbridge-pallet-outbound-queue-v2?/runtime-benchmarks", "snowbridge-pallet-outbound-queue?/runtime-benchmarks", "snowbridge-pallet-system?/runtime-benchmarks", "snowbridge-router-primitives?/runtime-benchmarks", @@ -510,6 +510,7 @@ serde = [ "pallet-treasury?/serde", "pallet-xcm?/serde", "snowbridge-beacon-primitives?/serde", + "snowbridge-core?/serde", "snowbridge-ethereum?/serde", "snowbridge-pallet-ethereum-client?/serde", "snowbridge-pallet-inbound-queue?/serde", @@ -545,7 +546,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-merkle-tree", "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api", "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -1442,11 +1443,31 @@ path = "../bridges/snowbridge/primitives/ethereum" default-features = false optional = true +[dependencies.snowbridge-merkle-tree] +path = "../bridges/snowbridge/primitives/merkle-tree" +default-features = false +optional = true + +[dependencies.snowbridge-outbound-primitives] +path = "../bridges/snowbridge/primitives/outbound" +default-features = false +optional = true + [dependencies.snowbridge-outbound-queue-runtime-api] path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" default-features = false optional = true +[dependencies.snowbridge-outbound-queue-runtime-api-v2] +path = "../bridges/snowbridge/pallets/outbound-queue-v2/runtime-api" +default-features = false +optional = true + +[dependencies.snowbridge-outbound-router-primitives] +path = "../bridges/snowbridge/primitives/outbound-router" +default-features = false +optional = true + [dependencies.snowbridge-pallet-ethereum-client] path = "../bridges/snowbridge/pallets/ethereum-client" default-features = false @@ -1472,6 +1493,11 @@ path = "../bridges/snowbridge/pallets/outbound-queue" default-features = false optional = true +[dependencies.snowbridge-pallet-outbound-queue-v2] +path = "../bridges/snowbridge/pallets/outbound-queue-v2" +default-features = false +optional = true + [dependencies.snowbridge-pallet-system] path = "../bridges/snowbridge/pallets/system" default-features = false @@ -1492,31 +1518,6 @@ path = "../bridges/snowbridge/pallets/system/runtime-api" default-features = false optional = true -[dependencies.snowbridge-merkle-tree] -path = "../bridges/snowbridge/primitives/merkle-tree" -default-features = false -optional = true - -[dependencies.snowbridge-outbound-primitives] -path = "../bridges/snowbridge/primitives/outbound" -default-features = false -optional = true - -[dependencies.snowbridge-outbound-router-primitives] -path = "../bridges/snowbridge/primitives/outbound-router" -default-features = false -optional = true - -[dependencies.snowbridge-pallet-outbound-queue-v2] -path = "../bridges/snowbridge/pallets/outbound-queue-v2" -default-features = false -optional = true - -[dependencies.snowbridge-outbound-queue-runtime-api-v2] -path = "../bridges/snowbridge/pallets/outbound-queue-v2/runtime-api" -default-features = false -optional = true - [dependencies.sp-api] path = "../substrate/primitives/api" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 036c1781c343..471a3141bac0 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -71,8 +71,7 @@ pub use bridge_hub_common; #[cfg(feature = "bridge-hub-test-utils")] pub use bridge_hub_test_utils; -/// Common types and functions that may be used by substrate-based runtimes of all bridged -/// chains. +/// Common types and functions that may be used by substrate-based runtimes of all bridged chains. #[cfg(feature = "bridge-runtime-common")] pub use bridge_runtime_common; @@ -104,8 +103,7 @@ pub use cumulus_client_consensus_relay_chain; #[cfg(feature = "cumulus-client-network")] pub use cumulus_client_network; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay -/// chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. #[cfg(feature = "cumulus-client-parachain-inherent")] pub use cumulus_client_parachain_inherent; @@ -161,8 +159,7 @@ pub use cumulus_primitives_aura; #[cfg(feature = "cumulus-primitives-core")] pub use cumulus_primitives_core; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay -/// chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. #[cfg(feature = "cumulus-primitives-parachain-inherent")] pub use cumulus_primitives_parachain_inherent; @@ -206,8 +203,7 @@ pub use cumulus_test_relay_sproof_builder; #[cfg(feature = "emulated-integration-tests-common")] pub use emulated_integration_tests_common; -/// Utility library for managing tree-like ordered data with logic for pruning the tree while -/// finalizing nodes. +/// Utility library for managing tree-like ordered data with logic for pruning the tree while finalizing nodes. #[cfg(feature = "fork-tree")] pub use fork_tree; @@ -239,8 +235,7 @@ pub use frame_executive; #[cfg(feature = "frame-metadata-hash-extension")] pub use frame_metadata_hash_extension; -/// An externalities provided environment that can load itself from remote nodes or cached -/// files. +/// An externalities provided environment that can load itself from remote nodes or cached files. #[cfg(feature = "frame-remote-externalities")] pub use frame_remote_externalities; @@ -336,8 +331,7 @@ pub use pallet_authority_discovery; #[cfg(feature = "pallet-authorship")] pub use pallet_authorship; -/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF -/// outputs and manages epoch transitions. +/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions. #[cfg(feature = "pallet-babe")] pub use pallet_babe; @@ -361,8 +355,7 @@ pub use pallet_beefy_mmr; #[cfg(feature = "pallet-bounties")] pub use pallet_bounties; -/// Module implementing GRANDPA on-chain light client used for bridging consensus of -/// substrate-based chains. +/// Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains. #[cfg(feature = "pallet-bridge-grandpa")] pub use pallet_bridge_grandpa; @@ -390,8 +383,7 @@ pub use pallet_child_bounties; #[cfg(feature = "pallet-collator-selection")] pub use pallet_collator_selection; -/// Collective system: Members of a set of account IDs can make their collective feelings known -/// through dispatched calls from one of two specialized origins. +/// Collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-collective")] pub use pallet_collective; @@ -559,8 +551,7 @@ pub use pallet_preimage; #[cfg(feature = "pallet-proxy")] pub use pallet_proxy; -/// Ranked collective system: Members of a set of account IDs can make their collective -/// feelings known through dispatched calls from one of two specialized origins. +/// Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-ranked-collective")] pub use pallet_ranked_collective; @@ -628,8 +619,7 @@ pub use pallet_session; #[cfg(feature = "pallet-session-benchmarking")] pub use pallet_session_benchmarking; -/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions -/// are satisfied. +/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions are satisfied. #[cfg(feature = "pallet-skip-feeless-payment")] pub use pallet_skip_feeless_payment; @@ -741,23 +731,19 @@ pub use parachains_common; #[cfg(feature = "parachains-runtimes-test-utils")] pub use parachains_runtimes_test_utils; -/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals -/// for approval checks on candidates over the network. +/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals for approval checks on candidates over the network. #[cfg(feature = "polkadot-approval-distribution")] pub use polkadot_approval_distribution; -/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used -/// to compactly determine which backed candidates are available or not based on a 2/3+ quorum. +/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used to compactly determine which backed candidates are available or not based on a 2/3+ quorum. #[cfg(feature = "polkadot-availability-bitfield-distribution")] pub use polkadot_availability_bitfield_distribution; -/// The Availability Distribution subsystem. Requests the required availability data. Also -/// distributes availability data and chunks to requesters. +/// The Availability Distribution subsystem. Requests the required availability data. Also distributes availability data and chunks to requesters. #[cfg(feature = "polkadot-availability-distribution")] pub use polkadot_availability_distribution; -/// The Availability Recovery subsystem. Handles requests for recovering the availability data -/// of included candidates. +/// The Availability Recovery subsystem. Handles requests for recovering the availability data of included candidates. #[cfg(feature = "polkadot-availability-recovery")] pub use polkadot_availability_recovery; @@ -765,8 +751,7 @@ pub use polkadot_availability_recovery; #[cfg(feature = "polkadot-cli")] pub use polkadot_cli; -/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each -/// other. +/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each other. #[cfg(feature = "polkadot-collator-protocol")] pub use polkadot_collator_protocol; @@ -774,8 +759,7 @@ pub use polkadot_collator_protocol; #[cfg(feature = "polkadot-core-primitives")] pub use polkadot_core_primitives; -/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware -/// of a dispute and have the relevant votes. +/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware of a dispute and have the relevant votes. #[cfg(feature = "polkadot-dispute-distribution")] pub use polkadot_dispute_distribution; @@ -783,8 +767,7 @@ pub use polkadot_dispute_distribution; #[cfg(feature = "polkadot-erasure-coding")] pub use polkadot_erasure_coding; -/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and -/// issuing a connection request to the relevant validators on every new session. +/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and issuing a connection request to the relevant validators on every new session. #[cfg(feature = "polkadot-gossip-support")] pub use polkadot_gossip_support; @@ -804,13 +787,11 @@ pub use polkadot_node_core_approval_voting; #[cfg(feature = "polkadot-node-core-approval-voting-parallel")] pub use polkadot_node_core_approval_voting_parallel; -/// The Availability Store subsystem. Wrapper over the DB that stores availability data and -/// chunks. +/// The Availability Store subsystem. Wrapper over the DB that stores availability data and chunks. #[cfg(feature = "polkadot-node-core-av-store")] pub use polkadot_node_core_av_store; -/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as -/// the issuance of statements about candidates. +/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates. #[cfg(feature = "polkadot-node-core-backing")] pub use polkadot_node_core_backing; @@ -818,13 +799,11 @@ pub use polkadot_node_core_backing; #[cfg(feature = "polkadot-node-core-bitfield-signing")] pub use polkadot_node_core_bitfield_signing; -/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to -/// validate candidates according to a PVF. +/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to validate candidates according to a PVF. #[cfg(feature = "polkadot-node-core-candidate-validation")] pub use polkadot_node_core_candidate_validation; -/// The Chain API subsystem provides access to chain related utility functions like block -/// number to hash conversions. +/// The Chain API subsystem provides access to chain related utility functions like block number to hash conversions. #[cfg(feature = "polkadot-node-core-chain-api")] pub use polkadot_node_core_chain_api; @@ -844,33 +823,27 @@ pub use polkadot_node_core_parachains_inherent; #[cfg(feature = "polkadot-node-core-prospective-parachains")] pub use polkadot_node_core_prospective_parachains; -/// Responsible for assembling a relay chain block from a set of available parachain -/// candidates. +/// Responsible for assembling a relay chain block from a set of available parachain candidates. #[cfg(feature = "polkadot-node-core-provisioner")] pub use polkadot_node_core_provisioner; -/// Polkadot crate that implements the PVF validation host. Responsible for coordinating -/// preparation and execution of PVFs. +/// Polkadot crate that implements the PVF validation host. Responsible for coordinating preparation and execution of PVFs. #[cfg(feature = "polkadot-node-core-pvf")] pub use polkadot_node_core_pvf; -/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and -/// voting for PVFs that are pending approval. +/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and voting for PVFs that are pending approval. #[cfg(feature = "polkadot-node-core-pvf-checker")] pub use polkadot_node_core_pvf_checker; -/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host -/// and the PVF workers. +/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host and the PVF workers. #[cfg(feature = "polkadot-node-core-pvf-common")] pub use polkadot_node_core_pvf_common; -/// Polkadot crate that contains the logic for executing PVFs. Used by the -/// polkadot-execute-worker binary. +/// Polkadot crate that contains the logic for executing PVFs. Used by the polkadot-execute-worker binary. #[cfg(feature = "polkadot-node-core-pvf-execute-worker")] pub use polkadot_node_core_pvf_execute_worker; -/// Polkadot crate that contains the logic for preparing PVFs. Used by the -/// polkadot-prepare-worker binary. +/// Polkadot crate that contains the logic for preparing PVFs. Used by the polkadot-prepare-worker binary. #[cfg(feature = "polkadot-node-core-pvf-prepare-worker")] pub use polkadot_node_core_pvf_prepare_worker; @@ -934,8 +907,7 @@ pub use polkadot_runtime_metrics; #[cfg(feature = "polkadot-runtime-parachains")] pub use polkadot_runtime_parachains; -/// Experimental: The single package to get you started with building frame pallets and -/// runtimes. +/// Experimental: The single package to get you started with building frame pallets and runtimes. #[cfg(feature = "polkadot-sdk-frame")] pub use polkadot_sdk_frame; @@ -1119,8 +1091,7 @@ pub use sc_rpc_server; #[cfg(feature = "sc-rpc-spec-v2")] pub use sc_rpc_spec_v2; -/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. -/// Manages communication between them. +/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. Manages communication between them. #[cfg(feature = "sc-service")] pub use sc_service; @@ -1184,10 +1155,26 @@ pub use snowbridge_core; #[cfg(feature = "snowbridge-ethereum")] pub use snowbridge_ethereum; +/// Snowbridge Merkle Tree. +#[cfg(feature = "snowbridge-merkle-tree")] +pub use snowbridge_merkle_tree; + +/// Snowbridge outbound primitives. +#[cfg(feature = "snowbridge-outbound-primitives")] +pub use snowbridge_outbound_primitives; + /// Snowbridge Outbound Queue Runtime API. #[cfg(feature = "snowbridge-outbound-queue-runtime-api")] pub use snowbridge_outbound_queue_runtime_api; +/// Snowbridge Outbound Queue Runtime API V2. +#[cfg(feature = "snowbridge-outbound-queue-runtime-api-v2")] +pub use snowbridge_outbound_queue_runtime_api_v2; + +/// Snowbridge Router Primitives. +#[cfg(feature = "snowbridge-outbound-router-primitives")] +pub use snowbridge_outbound_router_primitives; + /// Snowbridge Ethereum Client Pallet. #[cfg(feature = "snowbridge-pallet-ethereum-client")] pub use snowbridge_pallet_ethereum_client; @@ -1208,6 +1195,10 @@ pub use snowbridge_pallet_inbound_queue_fixtures; #[cfg(feature = "snowbridge-pallet-outbound-queue")] pub use snowbridge_pallet_outbound_queue; +/// Snowbridge Outbound Queue Pallet V2. +#[cfg(feature = "snowbridge-pallet-outbound-queue-v2")] +pub use snowbridge_pallet_outbound_queue_v2; + /// Snowbridge System Pallet. #[cfg(feature = "snowbridge-pallet-system")] pub use snowbridge_pallet_system; @@ -1228,26 +1219,6 @@ pub use snowbridge_runtime_test_common; #[cfg(feature = "snowbridge-system-runtime-api")] pub use snowbridge_system_runtime_api; -/// Snowbridge merkle tree. -#[cfg(feature = "snowbridge-merkle-tree")] -pub use snowbridge_merkle_tree; - -/// Snowbridge Outbound primitives. -#[cfg(feature = "snowbridge-outbound-primitives")] -pub use snowbridge_outbound_primitives; - -/// Snowbridge Outbound router primitives. -#[cfg(feature = "snowbridge-outbound-router-primitives")] -pub use snowbridge_outbound_router_primitives; - -/// Snowbridge Outbound Queue Runtime API. -#[cfg(feature = "snowbridge-outbound-queue-runtime-api-v2")] -pub use snowbridge_outbound_queue_runtime_api_v2; - -/// Snowbridge Outbound Queue Pallet V2. -#[cfg(feature = "snowbridge-pallet-outbound-queue-v2")] -pub use snowbridge_pallet_outbound_queue_v2; - /// Substrate runtime api primitives. #[cfg(feature = "sp-api")] pub use sp_api; @@ -1312,8 +1283,7 @@ pub use sp_core; #[cfg(feature = "sp-core-hashing")] pub use sp_core_hashing; -/// Procedural macros for calculating static hashes (deprecated in favor of -/// `sp-crypto-hashing-proc-macro`). +/// Procedural macros for calculating static hashes (deprecated in favor of `sp-crypto-hashing-proc-macro`). #[cfg(feature = "sp-core-hashing-proc-macro")] pub use sp_core_hashing_proc_macro; @@ -1401,8 +1371,7 @@ pub use sp_runtime; #[cfg(feature = "sp-runtime-interface")] pub use sp_runtime_interface; -/// This crate provides procedural macros for usage within the context of the Substrate runtime -/// interface. +/// This crate provides procedural macros for usage within the context of the Substrate runtime interface. #[cfg(feature = "sp-runtime-interface-proc-macro")] pub use sp_runtime_interface_proc_macro; @@ -1410,8 +1379,7 @@ pub use sp_runtime_interface_proc_macro; #[cfg(feature = "sp-session")] pub use sp_session; -/// A crate which contains primitives that are useful for implementation that uses staking -/// approaches in general. Definitions related to sessions, slashing, etc go here. +/// A crate which contains primitives that are useful for implementation that uses staking approaches in general. Definitions related to sessions, slashing, etc go here. #[cfg(feature = "sp-staking")] pub use sp_staking; @@ -1423,8 +1391,7 @@ pub use sp_state_machine; #[cfg(feature = "sp-statement-store")] pub use sp_statement_store; -/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std -/// or client/alloc to be used with any code that depends on the runtime. +/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std or client/alloc to be used with any code that depends on the runtime. #[cfg(feature = "sp-std")] pub use sp_std; @@ -1452,8 +1419,7 @@ pub use sp_transaction_storage_proof; #[cfg(feature = "sp-trie")] pub use sp_trie; -/// Version module for the Substrate runtime; Provides a function that returns the runtime -/// version. +/// Version module for the Substrate runtime; Provides a function that returns the runtime version. #[cfg(feature = "sp-version")] pub use sp_version; @@ -1469,8 +1435,7 @@ pub use sp_wasm_interface; #[cfg(feature = "sp-weights")] pub use sp_weights; -/// Utility for building chain-specification files for Substrate-based runtimes based on -/// `sp-genesis-builder`. +/// Utility for building chain-specification files for Substrate-based runtimes based on `sp-genesis-builder`. #[cfg(feature = "staging-chain-spec-builder")] pub use staging_chain_spec_builder; @@ -1498,8 +1463,7 @@ pub use staging_xcm_builder; #[cfg(feature = "staging-xcm-executor")] pub use staging_xcm_executor; -/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing -/// number of parachains and Substrate based projects. +/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing number of parachains and Substrate based projects. #[cfg(feature = "subkey")] pub use subkey; @@ -1543,8 +1507,7 @@ pub use testnet_parachains_constants; #[cfg(feature = "tracing-gum")] pub use tracing_gum; -/// Generate an overseer including builder pattern and message wrapper from a single annotated -/// struct definition. +/// Generate an overseer including builder pattern and message wrapper from a single annotated struct definition. #[cfg(feature = "tracing-gum-proc-macro")] pub use tracing_gum_proc_macro; From 4eaa6b3e26d2a0c335c48e031aeac27170ce4830 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 16:26:30 +0800 Subject: [PATCH 51/81] Fix doc --- bridges/snowbridge/pallets/outbound-queue/src/lib.rs | 4 ++-- .../pallets/outbound-queue/src/send_message_impl.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs index 08a8937fbc9b..9c885c36a963 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs @@ -12,9 +12,9 @@ //! //! The message submission pipeline works like this: //! 1. The message is first validated via the implementation for -//! [`snowbridge_core::outbound::SendMessage::validate`] +//! [`snowbridge_outbound_primitives::v1::SendMessage::validate`] //! 2. The message is then enqueued for later processing via the implementation for -//! [`snowbridge_core::outbound::SendMessage::deliver`] +//! [`snowbridge_outbound_primitives::v1::SendMessage::deliver`] //! 3. The underlying message queue is implemented by [`Config::MessageQueue`] //! 4. The message queue delivers messages back to this pallet via the implementation for //! [`frame_support::traits::ProcessMessage::process_message`] diff --git a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs index f3b79cdf91c4..ace5ea69145f 100644 --- a/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs +++ b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Implementation for [`snowbridge_core::outbound::SendMessage`] +//! Implementation for [`snowbridge_outbound_primitives::v1::SendMessage`] use super::*; use bridge_hub_common::AggregateMessageOrigin; use codec::Encode; From a1ae3daf228ce63f8cf9e06ad27ed16d9a8973e0 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 21:33:34 +0800 Subject: [PATCH 52/81] Upgrade alloy-core --- Cargo.lock | 6 +-- .../pallets/outbound-queue-v2/Cargo.toml | 6 +-- .../outbound-queue-v2/runtime-api/src/lib.rs | 2 +- .../pallets/outbound-queue-v2/src/api.rs | 13 ++--- .../pallets/outbound-queue-v2/src/envelope.rs | 5 +- .../pallets/outbound-queue-v2/src/lib.rs | 53 +++++++++++-------- .../snowbridge/primitives/outbound/Cargo.toml | 6 +-- .../snowbridge/primitives/outbound/src/v2.rs | 50 +++++++++-------- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- 9 files changed, 73 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 781cf745050d..ac33c8353d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25055,8 +25055,7 @@ dependencies = [ name = "snowbridge-outbound-primitives" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-core", "ethabi-decode 2.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -25355,8 +25354,7 @@ dependencies = [ name = "snowbridge-pallet-outbound-queue-v2" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-core", "bridge-hub-common 0.1.0", "ethabi-decode 2.0.0", "frame-benchmarking 28.0.0", diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml index 1f5c6c84c766..bed6ffe22b44 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml @@ -18,8 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { features = ["alloc", "derive"], workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } +alloy-core = { workspace = true, features = ["sol-types"] } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } @@ -49,8 +48,7 @@ sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", + "alloy-core/std", "bridge-hub-common/std", "codec/std", "ethabi/std", diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index 955c37892e7e..a9b952e43557 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -4,7 +4,7 @@ use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_merkle_tree::MerkleProof; -use snowbridge_outbound_primitives::{v2::abi::InboundMessage, DryRunError}; +use snowbridge_outbound_primitives::{v2::InboundMessage, DryRunError}; use xcm::prelude::Xcm; sp_api::decl_runtime_apis! { diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 2b046ed0b883..9869632f7192 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -6,10 +6,7 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; use snowbridge_outbound_primitives::{ - v2::{ - abi::{CommandWrapper, InboundMessage}, - GasMeter, Message, - }, + v2::{GasMeter, InboundCommandWrapper, InboundMessage, Message}, DryRunError, }; use snowbridge_outbound_router_primitives::v2::convert::XcmConverter; @@ -40,21 +37,21 @@ where let fee = crate::Pallet::::calculate_local_fee(); - let commands: Vec = message + let commands: Vec = message .commands .into_iter() - .map(|command| CommandWrapper { + .map(|command| InboundCommandWrapper { kind: command.index(), gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), payload: command.abi_encode(), }) .collect(); - let committed_message = InboundMessage { + let message = InboundMessage { origin: message.origin, nonce: Default::default(), commands: commands.try_into().map_err(|_| DryRunError::ConvertXcmFailed)?, }; - Ok((committed_message, fee)) + Ok((message, fee)) } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs index 744c93deb796..dfd449f83e74 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs @@ -6,8 +6,7 @@ use sp_core::{RuntimeDebug, H160}; use sp_std::prelude::*; use crate::Config; -use alloy_primitives::B256; -use alloy_sol_types::{sol, SolEvent}; +use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; use codec::Decode; use frame_support::pallet_prelude::{Encode, TypeInfo}; @@ -40,7 +39,7 @@ impl TryFrom<&Log> for Envelope { fn try_from(log: &Log) -> Result { let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = InboundMessageDispatched::decode_log(topics, &log.data, true) + let event = InboundMessageDispatched::decode_raw_log(topics, &log.data, true) .map_err(|_| EnvelopeDecodeError::DecodeLogFailed)?; let account = T::AccountId::decode(&mut &event.reward_address[..]) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index e1cd08a20efe..7762d3b71533 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -60,8 +60,10 @@ mod mock; #[cfg(test)] mod test; -use alloy_primitives::FixedBytes; -use alloy_sol_types::SolValue; +use alloy_core::{ + primitives::{Bytes, FixedBytes}, + sol_types::SolValue, +}; use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem}; use codec::Decode; use envelope::Envelope; @@ -77,8 +79,8 @@ use snowbridge_core::{ }; use snowbridge_merkle_tree::merkle_root; use snowbridge_outbound_primitives::v2::{ - abi::{CommandWrapper, InboundMessage, InboundMessageWrapper}, - GasMeter, Message, + abi::{CommandWrapper, InboundMessageWrapper}, + GasMeter, InboundCommandWrapper, InboundMessage, Message, }; use sp_core::{H160, H256}; use sp_runtime::{ @@ -326,41 +328,48 @@ pub mod pallet { Yield ); - // Decode bytes into versioned message - let message: Message = Message::decode(&mut message).map_err(|_| Corrupt)?; - let nonce = Nonce::::get(); - let commands: Vec = message + // Decode bytes into Message and + // a. Convert to InboundMessage and save into Messages + // b. Convert to committed hash and save into MessageLeaves + // c. Save nonce&fee into PendingOrders + let message: Message = Message::decode(&mut message).map_err(|_| Corrupt)?; + let commands: Vec = message .commands .clone() .into_iter() - .map(|command| CommandWrapper { + .map(|command| InboundCommandWrapper { kind: command.index(), gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), payload: command.abi_encode(), }) .collect(); - // Construct the final committed message - let inbound_message = InboundMessage { - origin: message.origin, - nonce, - commands: commands.clone().try_into().map_err(|_| Corrupt)?, - }; - + let abi_commands: Vec = commands + .clone() + .into_iter() + .map(|command| CommandWrapper { + kind: command.kind, + gas: command.gas, + payload: Bytes::from(command.payload), + }) + .collect(); let committed_message = InboundMessageWrapper { origin: FixedBytes::from(message.origin.as_fixed_bytes()), nonce, - commands, + commands: abi_commands, }; + let message_abi_encoded_hash = + ::Hashing::hash(&committed_message.abi_encode()); + MessageLeaves::::append(message_abi_encoded_hash); - // ABI-encode and hash the prepared message - let message_abi_encoded = committed_message.abi_encode(); - let message_abi_encoded_hash = ::Hashing::hash(&message_abi_encoded); - + let inbound_message = InboundMessage { + origin: message.origin, + nonce, + commands: commands.try_into().map_err(|_| Corrupt)?, + }; Messages::::append(Box::new(inbound_message)); - MessageLeaves::::append(message_abi_encoded_hash); let order = PendingOrder { nonce, diff --git a/bridges/snowbridge/primitives/outbound/Cargo.toml b/bridges/snowbridge/primitives/outbound/Cargo.toml index 87af3fb3ffe5..8e8e8500acee 100644 --- a/bridges/snowbridge/primitives/outbound/Cargo.toml +++ b/bridges/snowbridge/primitives/outbound/Cargo.toml @@ -27,8 +27,7 @@ sp-core = { workspace = true } sp-arithmetic = { workspace = true } ethabi = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } +alloy-core = { workspace = true, features = ["sol-types"] } snowbridge-core = { workspace = true } @@ -39,8 +38,7 @@ xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", + "alloy-core/std", "codec/std", "ethabi/std", "frame-support/std", diff --git a/bridges/snowbridge/primitives/outbound/src/v2.rs b/bridges/snowbridge/primitives/outbound/src/v2.rs index 4b0add908528..ba5e49d47339 100644 --- a/bridges/snowbridge/primitives/outbound/src/v2.rs +++ b/bridges/snowbridge/primitives/outbound/src/v2.rs @@ -15,18 +15,14 @@ use abi::{ CallContractParams, MintForeignTokenParams, RegisterForeignTokenParams, SetOperatingModeParams, UnlockNativeTokenParams, UpgradeParams, }; -use alloy_primitives::{Address, FixedBytes, U256}; -use alloy_sol_types::SolValue; +use alloy_core::{ + primitives::{Address, Bytes, FixedBytes, U256}, + sol_types::SolValue, +}; use xcm::prelude::Location; pub mod abi { - use super::MAX_COMMANDS; - use alloy_sol_types::sol; - use codec::{Decode, Encode}; - use frame_support::BoundedVec; - use scale_info::TypeInfo; - use sp_core::{ConstU32, RuntimeDebug, H256}; - use sp_std::vec::Vec; + use alloy_core::sol; sol! { struct InboundMessageWrapper { @@ -38,7 +34,6 @@ pub mod abi { CommandWrapper[] commands; } - #[derive(Encode, Decode, RuntimeDebug, PartialEq,TypeInfo)] struct CommandWrapper { uint8 kind; uint64 gas; @@ -103,16 +98,23 @@ pub mod abi { uint256 value; } } +} - #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] - pub struct InboundMessage { - /// Origin - pub origin: H256, - /// Nonce - pub nonce: u64, - /// Commands - pub commands: BoundedVec>, - } +#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] +pub struct InboundCommandWrapper { + pub kind: u8, + pub gas: u64, + pub payload: Vec, +} + +#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] +pub struct InboundMessage { + /// Origin + pub origin: H256, + /// Nonce + pub nonce: u64, + /// Commands + pub commands: BoundedVec>, } pub const MAX_COMMANDS: u32 = 8; @@ -213,7 +215,9 @@ impl Command { Command::Upgrade { impl_address, impl_code_hash, initializer, .. } => UpgradeParams { implAddress: Address::from(impl_address.as_fixed_bytes()), implCodeHash: FixedBytes::from(impl_code_hash.as_fixed_bytes()), - initParams: initializer.clone().map_or(vec![], |i| i.params), + initParams: initializer + .clone() + .map_or(Bytes::from(vec![]), |i| Bytes::from(i.params)), } .abi_encode(), Command::CreateAgent {} => vec![], @@ -229,8 +233,8 @@ impl Command { Command::RegisterForeignToken { token_id, name, symbol, decimals } => RegisterForeignTokenParams { foreignTokenID: FixedBytes::from(token_id.as_fixed_bytes()), - name: name.to_vec(), - symbol: symbol.to_vec(), + name: Bytes::from(name.to_vec()), + symbol: Bytes::from(symbol.to_vec()), decimals: *decimals, } .abi_encode(), @@ -242,7 +246,7 @@ impl Command { .abi_encode(), Command::CallContract { target, data, value, .. } => CallContractParams { target: Address::from(target.as_fixed_bytes()), - data: data.to_vec(), + data: Bytes::from(data.to_vec()), value: U256::try_from(*value).unwrap(), } .abi_encode(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 1d837efa7c13..9140754da8ad 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -101,7 +101,7 @@ use parachains_common::{ use snowbridge_core::{AgentId, PricingParameters}; use snowbridge_outbound_primitives::{ v1::{Command, Fee}, - v2::abi::InboundMessage, + v2::InboundMessage, DryRunError, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; From bf44eb37f74abfbed9f5cf674f1cb97c681f916b Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 22:47:59 +0800 Subject: [PATCH 53/81] Assemble SystemFrontend into AH --- Cargo.lock | 2 + .../assets/asset-hub-westend/Cargo.toml | 6 ++ .../src/bridge_to_ethereum_config.rs | 65 +++++++++++++++++++ .../assets/asset-hub-westend/src/lib.rs | 5 ++ 4 files changed, 78 insertions(+) create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs diff --git a/Cargo.lock b/Cargo.lock index 9051655e564e..956c5efb06a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1186,12 +1186,14 @@ dependencies = [ "serde_json", "snowbridge-outbound-router-primitives", "snowbridge-router-primitives 0.9.0", + "snowbridge-system-frontend", "sp-api 26.0.0", "sp-block-builder 26.0.0", "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-genesis-builder 0.8.0", "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keyring 31.0.0", "sp-offchain 26.0.0", "sp-runtime 31.0.1", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index a5d1a5a14aa5..9fec24db94fb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -55,6 +55,7 @@ sp-core = { workspace = true } sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -101,6 +102,7 @@ bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-outbound-router-primitives = { workspace = true } +snowbridge-system-frontend = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } @@ -147,6 +149,7 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "snowbridge-router-primitives/runtime-benchmarks", + "snowbridge-system-frontend/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", @@ -186,6 +189,7 @@ try-runtime = [ "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", + "snowbridge-system-frontend/try-runtime", "sp-runtime/try-runtime", ] std = [ @@ -248,12 +252,14 @@ std = [ "serde_json/std", "snowbridge-outbound-router-primitives/std", "snowbridge-router-primitives/std", + "snowbridge-system-frontend/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-io/std", "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs new file mode 100644 index 000000000000..93a77fba0719 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -0,0 +1,65 @@ +// Copyright (C) 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 . +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::XcmRouter; +use crate::{xcm_config::AssetTransactors, Runtime, RuntimeEvent}; +use frame_support::traits::Everything; +use pallet_xcm::EnsureXcm; + +#[cfg(feature = "runtime-benchmarks")] +use benchmark_helpers::DoNothingRouter; +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmark_helpers { + use crate::RuntimeOrigin; + use codec::Encode; + use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; + + pub struct DoNothingRouter; + impl SendXcm for DoNothingRouter { + type Ticket = Xcm<()>; + + fn validate( + _dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + Ok((xcm.clone().unwrap(), Assets::new())) + } + fn deliver(xcm: Xcm<()>) -> Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } + } + + impl snowbridge_system_frontend::BenchmarkHelper for () { + fn make_xcm_origin(location: Location) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } + } +} + +impl snowbridge_system_frontend::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateAgentOrigin = EnsureXcm; + type RegisterTokenOrigin = EnsureXcm; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type AssetTransactor = AssetTransactors; +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index ffd54ce4c8ac..524dd4f421e1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -24,6 +24,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +mod bridge_to_ethereum_config; mod genesis_config_presets; mod weights; pub mod xcm_config; @@ -1042,6 +1043,9 @@ construct_runtime!( StateTrieMigration: pallet_state_trie_migration = 70, + // Snowbridge + SnowbridgeSystemFrontend: snowbridge_system_frontend = 80, + // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. AssetConversionMigration: pallet_asset_conversion_ops = 200, @@ -1337,6 +1341,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [snowbridge_system_frontend, SnowbridgeSystemFrontend] ); } From c4377b9d7fed36b0e7a40462c0bca47d7ad42a2b Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 23:20:57 +0800 Subject: [PATCH 54/81] Fix test --- bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index 8f53485328d1..f415d8f31e87 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -1,7 +1,7 @@ -use alloy_primitives::FixedBytes; // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork use crate::{mock::*, *}; +use alloy_core::primitives::FixedBytes; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -257,7 +257,7 @@ fn encode_mock_message() { .map(|command| CommandWrapper { kind: command.index(), gas: ::GasMeter::maximum_dispatch_gas_used_at_most(&command), - payload: command.abi_encode(), + payload: Bytes::from(command.abi_encode()), }) .collect(); From ec75b7bb960046347e73cf891054f37d1c95fed0 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 23:23:07 +0800 Subject: [PATCH 55/81] Fix format --- umbrella/src/lib.rs | 111 +++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 471a3141bac0..0ac71a2cee58 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -71,7 +71,8 @@ pub use bridge_hub_common; #[cfg(feature = "bridge-hub-test-utils")] pub use bridge_hub_test_utils; -/// Common types and functions that may be used by substrate-based runtimes of all bridged chains. +/// Common types and functions that may be used by substrate-based runtimes of all bridged +/// chains. #[cfg(feature = "bridge-runtime-common")] pub use bridge_runtime_common; @@ -103,7 +104,8 @@ pub use cumulus_client_consensus_relay_chain; #[cfg(feature = "cumulus-client-network")] pub use cumulus_client_network; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay +/// chain storage-proof. #[cfg(feature = "cumulus-client-parachain-inherent")] pub use cumulus_client_parachain_inherent; @@ -159,7 +161,8 @@ pub use cumulus_primitives_aura; #[cfg(feature = "cumulus-primitives-core")] pub use cumulus_primitives_core; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay +/// chain storage-proof. #[cfg(feature = "cumulus-primitives-parachain-inherent")] pub use cumulus_primitives_parachain_inherent; @@ -203,7 +206,8 @@ pub use cumulus_test_relay_sproof_builder; #[cfg(feature = "emulated-integration-tests-common")] pub use emulated_integration_tests_common; -/// Utility library for managing tree-like ordered data with logic for pruning the tree while finalizing nodes. +/// Utility library for managing tree-like ordered data with logic for pruning the tree while +/// finalizing nodes. #[cfg(feature = "fork-tree")] pub use fork_tree; @@ -235,7 +239,8 @@ pub use frame_executive; #[cfg(feature = "frame-metadata-hash-extension")] pub use frame_metadata_hash_extension; -/// An externalities provided environment that can load itself from remote nodes or cached files. +/// An externalities provided environment that can load itself from remote nodes or cached +/// files. #[cfg(feature = "frame-remote-externalities")] pub use frame_remote_externalities; @@ -331,7 +336,8 @@ pub use pallet_authority_discovery; #[cfg(feature = "pallet-authorship")] pub use pallet_authorship; -/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions. +/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF +/// outputs and manages epoch transitions. #[cfg(feature = "pallet-babe")] pub use pallet_babe; @@ -355,7 +361,8 @@ pub use pallet_beefy_mmr; #[cfg(feature = "pallet-bounties")] pub use pallet_bounties; -/// Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains. +/// Module implementing GRANDPA on-chain light client used for bridging consensus of +/// substrate-based chains. #[cfg(feature = "pallet-bridge-grandpa")] pub use pallet_bridge_grandpa; @@ -383,7 +390,8 @@ pub use pallet_child_bounties; #[cfg(feature = "pallet-collator-selection")] pub use pallet_collator_selection; -/// Collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. +/// Collective system: Members of a set of account IDs can make their collective feelings known +/// through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-collective")] pub use pallet_collective; @@ -551,7 +559,8 @@ pub use pallet_preimage; #[cfg(feature = "pallet-proxy")] pub use pallet_proxy; -/// Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. +/// Ranked collective system: Members of a set of account IDs can make their collective +/// feelings known through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-ranked-collective")] pub use pallet_ranked_collective; @@ -619,7 +628,8 @@ pub use pallet_session; #[cfg(feature = "pallet-session-benchmarking")] pub use pallet_session_benchmarking; -/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions are satisfied. +/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions +/// are satisfied. #[cfg(feature = "pallet-skip-feeless-payment")] pub use pallet_skip_feeless_payment; @@ -731,19 +741,23 @@ pub use parachains_common; #[cfg(feature = "parachains-runtimes-test-utils")] pub use parachains_runtimes_test_utils; -/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals for approval checks on candidates over the network. +/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals +/// for approval checks on candidates over the network. #[cfg(feature = "polkadot-approval-distribution")] pub use polkadot_approval_distribution; -/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used to compactly determine which backed candidates are available or not based on a 2/3+ quorum. +/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used +/// to compactly determine which backed candidates are available or not based on a 2/3+ quorum. #[cfg(feature = "polkadot-availability-bitfield-distribution")] pub use polkadot_availability_bitfield_distribution; -/// The Availability Distribution subsystem. Requests the required availability data. Also distributes availability data and chunks to requesters. +/// The Availability Distribution subsystem. Requests the required availability data. Also +/// distributes availability data and chunks to requesters. #[cfg(feature = "polkadot-availability-distribution")] pub use polkadot_availability_distribution; -/// The Availability Recovery subsystem. Handles requests for recovering the availability data of included candidates. +/// The Availability Recovery subsystem. Handles requests for recovering the availability data +/// of included candidates. #[cfg(feature = "polkadot-availability-recovery")] pub use polkadot_availability_recovery; @@ -751,7 +765,8 @@ pub use polkadot_availability_recovery; #[cfg(feature = "polkadot-cli")] pub use polkadot_cli; -/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each other. +/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each +/// other. #[cfg(feature = "polkadot-collator-protocol")] pub use polkadot_collator_protocol; @@ -759,7 +774,8 @@ pub use polkadot_collator_protocol; #[cfg(feature = "polkadot-core-primitives")] pub use polkadot_core_primitives; -/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware of a dispute and have the relevant votes. +/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware +/// of a dispute and have the relevant votes. #[cfg(feature = "polkadot-dispute-distribution")] pub use polkadot_dispute_distribution; @@ -767,7 +783,8 @@ pub use polkadot_dispute_distribution; #[cfg(feature = "polkadot-erasure-coding")] pub use polkadot_erasure_coding; -/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and issuing a connection request to the relevant validators on every new session. +/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and +/// issuing a connection request to the relevant validators on every new session. #[cfg(feature = "polkadot-gossip-support")] pub use polkadot_gossip_support; @@ -787,11 +804,13 @@ pub use polkadot_node_core_approval_voting; #[cfg(feature = "polkadot-node-core-approval-voting-parallel")] pub use polkadot_node_core_approval_voting_parallel; -/// The Availability Store subsystem. Wrapper over the DB that stores availability data and chunks. +/// The Availability Store subsystem. Wrapper over the DB that stores availability data and +/// chunks. #[cfg(feature = "polkadot-node-core-av-store")] pub use polkadot_node_core_av_store; -/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates. +/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as +/// the issuance of statements about candidates. #[cfg(feature = "polkadot-node-core-backing")] pub use polkadot_node_core_backing; @@ -799,11 +818,13 @@ pub use polkadot_node_core_backing; #[cfg(feature = "polkadot-node-core-bitfield-signing")] pub use polkadot_node_core_bitfield_signing; -/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to validate candidates according to a PVF. +/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to +/// validate candidates according to a PVF. #[cfg(feature = "polkadot-node-core-candidate-validation")] pub use polkadot_node_core_candidate_validation; -/// The Chain API subsystem provides access to chain related utility functions like block number to hash conversions. +/// The Chain API subsystem provides access to chain related utility functions like block +/// number to hash conversions. #[cfg(feature = "polkadot-node-core-chain-api")] pub use polkadot_node_core_chain_api; @@ -823,27 +844,33 @@ pub use polkadot_node_core_parachains_inherent; #[cfg(feature = "polkadot-node-core-prospective-parachains")] pub use polkadot_node_core_prospective_parachains; -/// Responsible for assembling a relay chain block from a set of available parachain candidates. +/// Responsible for assembling a relay chain block from a set of available parachain +/// candidates. #[cfg(feature = "polkadot-node-core-provisioner")] pub use polkadot_node_core_provisioner; -/// Polkadot crate that implements the PVF validation host. Responsible for coordinating preparation and execution of PVFs. +/// Polkadot crate that implements the PVF validation host. Responsible for coordinating +/// preparation and execution of PVFs. #[cfg(feature = "polkadot-node-core-pvf")] pub use polkadot_node_core_pvf; -/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and voting for PVFs that are pending approval. +/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and +/// voting for PVFs that are pending approval. #[cfg(feature = "polkadot-node-core-pvf-checker")] pub use polkadot_node_core_pvf_checker; -/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host and the PVF workers. +/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host +/// and the PVF workers. #[cfg(feature = "polkadot-node-core-pvf-common")] pub use polkadot_node_core_pvf_common; -/// Polkadot crate that contains the logic for executing PVFs. Used by the polkadot-execute-worker binary. +/// Polkadot crate that contains the logic for executing PVFs. Used by the +/// polkadot-execute-worker binary. #[cfg(feature = "polkadot-node-core-pvf-execute-worker")] pub use polkadot_node_core_pvf_execute_worker; -/// Polkadot crate that contains the logic for preparing PVFs. Used by the polkadot-prepare-worker binary. +/// Polkadot crate that contains the logic for preparing PVFs. Used by the +/// polkadot-prepare-worker binary. #[cfg(feature = "polkadot-node-core-pvf-prepare-worker")] pub use polkadot_node_core_pvf_prepare_worker; @@ -907,7 +934,8 @@ pub use polkadot_runtime_metrics; #[cfg(feature = "polkadot-runtime-parachains")] pub use polkadot_runtime_parachains; -/// Experimental: The single package to get you started with building frame pallets and runtimes. +/// Experimental: The single package to get you started with building frame pallets and +/// runtimes. #[cfg(feature = "polkadot-sdk-frame")] pub use polkadot_sdk_frame; @@ -1091,7 +1119,8 @@ pub use sc_rpc_server; #[cfg(feature = "sc-rpc-spec-v2")] pub use sc_rpc_spec_v2; -/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. Manages communication between them. +/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. +/// Manages communication between them. #[cfg(feature = "sc-service")] pub use sc_service; @@ -1283,7 +1312,8 @@ pub use sp_core; #[cfg(feature = "sp-core-hashing")] pub use sp_core_hashing; -/// Procedural macros for calculating static hashes (deprecated in favor of `sp-crypto-hashing-proc-macro`). +/// Procedural macros for calculating static hashes (deprecated in favor of +/// `sp-crypto-hashing-proc-macro`). #[cfg(feature = "sp-core-hashing-proc-macro")] pub use sp_core_hashing_proc_macro; @@ -1371,7 +1401,8 @@ pub use sp_runtime; #[cfg(feature = "sp-runtime-interface")] pub use sp_runtime_interface; -/// This crate provides procedural macros for usage within the context of the Substrate runtime interface. +/// This crate provides procedural macros for usage within the context of the Substrate runtime +/// interface. #[cfg(feature = "sp-runtime-interface-proc-macro")] pub use sp_runtime_interface_proc_macro; @@ -1379,7 +1410,8 @@ pub use sp_runtime_interface_proc_macro; #[cfg(feature = "sp-session")] pub use sp_session; -/// A crate which contains primitives that are useful for implementation that uses staking approaches in general. Definitions related to sessions, slashing, etc go here. +/// A crate which contains primitives that are useful for implementation that uses staking +/// approaches in general. Definitions related to sessions, slashing, etc go here. #[cfg(feature = "sp-staking")] pub use sp_staking; @@ -1391,7 +1423,8 @@ pub use sp_state_machine; #[cfg(feature = "sp-statement-store")] pub use sp_statement_store; -/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std or client/alloc to be used with any code that depends on the runtime. +/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std +/// or client/alloc to be used with any code that depends on the runtime. #[cfg(feature = "sp-std")] pub use sp_std; @@ -1419,7 +1452,8 @@ pub use sp_transaction_storage_proof; #[cfg(feature = "sp-trie")] pub use sp_trie; -/// Version module for the Substrate runtime; Provides a function that returns the runtime version. +/// Version module for the Substrate runtime; Provides a function that returns the runtime +/// version. #[cfg(feature = "sp-version")] pub use sp_version; @@ -1435,7 +1469,8 @@ pub use sp_wasm_interface; #[cfg(feature = "sp-weights")] pub use sp_weights; -/// Utility for building chain-specification files for Substrate-based runtimes based on `sp-genesis-builder`. +/// Utility for building chain-specification files for Substrate-based runtimes based on +/// `sp-genesis-builder`. #[cfg(feature = "staging-chain-spec-builder")] pub use staging_chain_spec_builder; @@ -1463,7 +1498,8 @@ pub use staging_xcm_builder; #[cfg(feature = "staging-xcm-executor")] pub use staging_xcm_executor; -/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing number of parachains and Substrate based projects. +/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing +/// number of parachains and Substrate based projects. #[cfg(feature = "subkey")] pub use subkey; @@ -1507,7 +1543,8 @@ pub use testnet_parachains_constants; #[cfg(feature = "tracing-gum")] pub use tracing_gum; -/// Generate an overseer including builder pattern and message wrapper from a single annotated struct definition. +/// Generate an overseer including builder pattern and message wrapper from a single annotated +/// struct definition. #[cfg(feature = "tracing-gum-proc-macro")] pub use tracing_gum_proc_macro; From b020544c1bca7f3afd9cc09f99d4dc6104591655 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 12 Dec 2024 23:25:55 +0800 Subject: [PATCH 56/81] Fix license --- .../src/tests/snowbridge_common.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index 2d12b7fe7ee4..2f7099873277 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -1,3 +1,18 @@ +// Copyright (C) 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::{ create_pool_with_native_on, imports::*, From 97df62569834e47bb743e3f6e2a593bdc5178068 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 13 Dec 2024 02:27:36 +0800 Subject: [PATCH 57/81] Add integration tests --- .../pallets/system-frontend/src/lib.rs | 23 ++-- .../pallets/system-frontend/src/mock.rs | 19 ++- .../snowbridge/pallets/system-v2/src/lib.rs | 9 +- .../src/tests/snowbridge_common.rs | 19 +++ .../src/tests/snowbridge_v2_outbound.rs | 108 +++++++++++++++++- .../src/bridge_to_ethereum_config.rs | 27 ++++- 6 files changed, 190 insertions(+), 15 deletions(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index ef09057fcba2..72a2010e9b25 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -80,6 +80,10 @@ pub mod pallet { #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; + type WETH: Get; + + type DeliveryFee: Get; + type WeightInfo: WeightInfo; } @@ -113,7 +117,7 @@ pub mod pallet { pub fn create_agent(origin: OriginFor, fee: u128) -> DispatchResult { let origin_location = T::CreateAgentOrigin::ensure_origin(origin)?; - Self::burn_weth(origin_location.clone(), fee)?; + Self::burn_fees(origin_location.clone(), fee)?; let call = SnowbridgeControl::Control(ControlCall::CreateAgent { location: Box::new(VersionedLocation::from(origin_location.clone())), @@ -121,7 +125,8 @@ pub mod pallet { }); let xcm: Xcm<()> = vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + ReceiveTeleportedAsset(T::DeliveryFee::get().into()), + PayFees { asset: T::DeliveryFee::get() }, Transact { origin_kind: OriginKind::Xcm, call: call.encode().into(), @@ -159,7 +164,7 @@ pub mod pallet { } ensure!(checked, >::OwnerCheck); - Self::burn_weth(origin_location.clone(), fee)?; + Self::burn_fees(origin_location.clone(), fee)?; let call = SnowbridgeControl::Control(ControlCall::RegisterToken { asset_id: Box::new(VersionedLocation::from(asset_location.clone())), @@ -168,7 +173,8 @@ pub mod pallet { }); let xcm: Xcm<()> = vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + ReceiveTeleportedAsset(T::DeliveryFee::get().into()), + PayFees { asset: T::DeliveryFee::get() }, Transact { origin_kind: OriginKind::Xcm, call: call.encode().into(), @@ -191,10 +197,11 @@ pub mod pallet { send_xcm::(bridgehub, xcm).map_err(|_| Error::::Send)?; Ok(()) } - pub fn burn_weth(origin_location: Location, fee: u128) -> DispatchResult { - let fee_asset = - (Location::new(2, [GlobalConsensus(Ethereum { chain_id: 1 })]), fee).into(); - T::AssetTransactor::withdraw_asset(&fee_asset, &origin_location, None) + pub fn burn_fees(origin_location: Location, fee: u128) -> DispatchResult { + let ethereum_fee_asset = (T::WETH::get(), fee).into(); + T::AssetTransactor::withdraw_asset(ðereum_fee_asset, &origin_location, None) + .map_err(|_| Error::::FundsUnavailable)?; + T::AssetTransactor::withdraw_asset(&T::DeliveryFee::get(), &origin_location, None) .map_err(|_| Error::::FundsUnavailable)?; Ok(()) } diff --git a/bridges/snowbridge/pallets/system-frontend/src/mock.rs b/bridges/snowbridge/pallets/system-frontend/src/mock.rs index 774a43d51560..027ebb17ce32 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/mock.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/mock.rs @@ -3,7 +3,8 @@ use crate as snowbridge_system_frontend; use crate::mock::pallet_xcm_origin::EnsureXcm; use codec::Encode; -use frame_support::{derive_impl, traits::Everything}; +use frame_support::{derive_impl, parameter_types, traits::Everything}; +use hex_literal::hex; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, @@ -167,6 +168,20 @@ impl TransactAsset for SuccessfulTransactor { } } +parameter_types! { + pub storage WETH: Location = Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: 11155111 }), + AccountKey20 { + network: None, + key: hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), + }, + ], + ); + pub storage DeliveryFee: Asset = (Location::parent(), 80_000_000_000u128).into(); +} + impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); @@ -176,6 +191,8 @@ impl crate::Config for Test { type RegisterTokenOrigin = EnsureXcm; type XcmSender = MockXcmSender; type AssetTransactor = SuccessfulTransactor; + type WETH = WETH; + type DeliveryFee = DeliveryFee; } // Build genesis storage according to the mock runtime. diff --git a/bridges/snowbridge/pallets/system-v2/src/lib.rs b/bridges/snowbridge/pallets/system-v2/src/lib.rs index c94793d2c890..49c78e29a5c4 100644 --- a/bridges/snowbridge/pallets/system-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/system-v2/src/lib.rs @@ -153,7 +153,14 @@ pub mod pallet { let origin_location: Location = (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; - let agent_id = agent_id_of::(&origin_location)?; + let ethereum_location = T::EthereumLocation::get(); + // reanchor to Ethereum context + let location = origin_location + .clone() + .reanchored(ðereum_location, &T::UniversalLocation::get()) + .map_err(|_| Error::::LocationConversionFailed)?; + + let agent_id = agent_id_of::(&location)?; // Record the agent id or fail if it has already been created ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index 2f7099873277..88d261cc105d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -18,6 +18,7 @@ use crate::{ imports::*, tests::snowbridge::{CHAIN_ID, WETH}, }; +use asset_hub_westend_runtime::xcm_config::LocationToAccountId; use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; use frame_support::traits::fungibles::Mutate; use hex_literal::hex; @@ -254,6 +255,17 @@ pub fn fund_on_ah() { let penpal_sovereign = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalB::para_id()), ); + let penpal_user_sovereign = LocationToAccountId::convert_location(&Location::new( + 1, + [ + Parachain(PenpalB::para_id().into()), + AccountId32 { + network: Some(ByGenesis(WESTEND_GENESIS_HASH)), + id: PenpalBSender::get().into(), + }, + ], + )) + .unwrap(); AssetHubWestend::execute_with(|| { assert_ok!(::ForeignAssets::mint_into( @@ -261,6 +273,11 @@ pub fn fund_on_ah() { &penpal_sovereign, INITIAL_FUND, )); + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &penpal_user_sovereign, + INITIAL_FUND, + )); assert_ok!(::ForeignAssets::mint_into( weth_location().try_into().unwrap(), &AssetHubWestendReceiver::get(), @@ -281,6 +298,8 @@ pub fn fund_on_ah() { .unwrap() .into(); AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(penpal_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![(penpal_user_sovereign.clone(), INITIAL_FUND)]); } pub fn create_pools_on_ah() { diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 4df72dc4c5a0..9465f09d6985 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -17,8 +17,10 @@ use crate::{ imports::*, tests::{snowbridge::WETH, snowbridge_common::*}, }; -use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; +use emulated_integration_tests_common::{impls::Decode, PenpalBTeleportableAssetLocation}; +use frame_support::pallet_prelude::TypeInfo; use rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::LocalTeleportableToAssetHub; +use snowbridge_core::AssetMetadata; use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; @@ -463,3 +465,107 @@ fn send_message_from_penpal_to_ethereum_with_sudo() { fn send_message_from_penpal_to_ethereum_with_user_origin() { send_message_from_penpal_to_ethereum(false) } + +#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] +pub enum ControlFrontendCall { + #[codec(index = 1)] + CreateAgent { fee: u128 }, + #[codec(index = 2)] + RegisterToken { asset_id: Box, metadata: AssetMetadata, fee: u128 }, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)] +pub enum SnowbridgeControlFrontend { + #[codec(index = 80)] + Control(ControlFrontendCall), +} + +#[test] +fn create_user_agent_from_penpal() { + fund_on_bh(); + register_weth_on_ah(); + fund_on_ah(); + create_pools_on_ah(); + set_trust_reserve_on_penpal(); + register_weth_on_penpal(); + fund_on_penpal(); + let penpal_user_location = Location::new( + 1, + [ + Parachain(PenpalB::para_id().into()), + AccountId32 { + network: Some(ByGenesis(WESTEND_GENESIS_HASH)), + id: PenpalBSender::get().into(), + }, + ], + ); + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + let local_fee_asset_on_penpal = + Asset { id: AssetId(Location::parent()), fun: Fungible(LOCAL_FEE_AMOUNT_IN_DOT) }; + + let remote_fee_asset_on_ah = + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; + + let remote_fee_asset_on_ethereum = + Asset { id: AssetId(weth_location()), fun: Fungible(REMOTE_FEE_AMOUNT_IN_WETH) }; + + let call = SnowbridgeControlFrontend::Control(ControlFrontendCall::CreateAgent { + fee: REMOTE_FEE_AMOUNT_IN_WETH, + }); + + let assets = vec![ + local_fee_asset_on_penpal.clone(), + remote_fee_asset_on_ah.clone(), + remote_fee_asset_on_ethereum.clone(), + ]; + + let xcm = VersionedXcm::from(Xcm(vec![ + WithdrawAsset(assets.clone().into()), + PayFees { asset: local_fee_asset_on_penpal.clone() }, + InitiateTransfer { + destination: asset_hub(), + remote_fees: Some(AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset_on_ah.clone().into(), + ))), + preserve_origin: true, + assets: vec![AssetTransferFilter::ReserveWithdraw(Definite( + remote_fee_asset_on_ethereum.clone().into(), + ))], + remote_xcm: Xcm(vec![ + DepositAsset { assets: Wild(All), beneficiary: penpal_user_location }, + Transact { + origin_kind: OriginKind::Xcm, + call: call.encode().into(), + fallback_max_weight: None, + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ]), + }, + ])); + + assert_ok!(::PolkadotXcm::execute( + RuntimeOrigin::signed(PenpalBSender::get()), + bx!(xcm.clone()), + Weight::from(EXECUTION_WEIGHT), + )); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { .. }) => {},] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::EthereumOutboundQueueV2(snowbridge_pallet_outbound_queue_v2::Event::MessageQueued{ .. }) => {},] + ); + }); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index 93a77fba0719..e679d1652a8d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -13,19 +13,22 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -#[cfg(not(feature = "runtime-benchmarks"))] -use crate::XcmRouter; + use crate::{xcm_config::AssetTransactors, Runtime, RuntimeEvent}; -use frame_support::traits::Everything; +use frame_support::{parameter_types, traits::Everything}; +use hex_literal::hex; use pallet_xcm::EnsureXcm; +use xcm::prelude::{AccountKey20, Asset, Ethereum, GlobalConsensus, Location, SendXcm}; +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::xcm_config::XcmRouter; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; + #[cfg(feature = "runtime-benchmarks")] pub mod benchmark_helpers { use crate::RuntimeOrigin; use codec::Encode; - use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash}; pub struct DoNothingRouter; impl SendXcm for DoNothingRouter { @@ -50,6 +53,20 @@ pub mod benchmark_helpers { } } +parameter_types! { + pub storage WETH: Location = Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: 11155111 }), + AccountKey20 { + network: None, + key: hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), + }, + ], + ); + pub storage DeliveryFee: Asset = (Location::parent(), 80_000_000_000u128).into(); +} + impl snowbridge_system_frontend::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); @@ -62,4 +79,6 @@ impl snowbridge_system_frontend::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; type AssetTransactor = AssetTransactors; + type WETH = WETH; + type DeliveryFee = DeliveryFee; } From b67f89a8fea69aef9af60ad80f59ac58820c1f4e Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 13 Dec 2024 03:10:21 +0800 Subject: [PATCH 58/81] Fix doc --- .../runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index 7bec58645064..c7d0eecf9314 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -33,7 +33,6 @@ use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::{bytes::to_hex, H160}; -use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, @@ -44,6 +43,7 @@ parameter_types! { } fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { + use sp_keyring::Sr25519Keyring::Alice; bridge_hub_test_utils::CollatorSessionKeys::new( AccountId::from(Alice), AccountId::from(Alice), From 290537ddbfad0b0078900db23d7c8638ae98c7b7 Mon Sep 17 00:00:00 2001 From: ron Date: Sat, 14 Dec 2024 03:53:40 +0000 Subject: [PATCH 59/81] Remove check of Ether value as agent can be prefunded in advance --- .../primitives/outbound-router/src/v2/convert.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs index 1e5a84ba0e81..41bc0f2dd9a0 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -181,7 +181,6 @@ where } let mut commands: Vec = Vec::new(); - let mut weth_amount = 0; // ENA transfer commands if let Some(enas) = enas { @@ -207,10 +206,6 @@ where // transfer amount must be greater than 0. ensure!(amount > 0, ZeroAssetTransfer); - if token == WETHAddress::get() { - weth_amount = amount; - } - commands.push(Command::UnlockNativeToken { token, recipient, amount }); } } @@ -251,9 +246,6 @@ where let transact = TransactInfo::decode_all(&mut transact_call.clone().into_encoded().as_slice()) .map_err(|_| TransactDecodeFailed)?; - if transact.value > 0 { - ensure!(weth_amount > transact.value, CallContractValueInsufficient); - } commands.push(Command::CallContract { target: transact.target, data: transact.data, From 473f5472698259cb08e0cc8a950cb438d04fd361 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 7 Jan 2025 10:29:47 +0800 Subject: [PATCH 60/81] Fix for umbrella --- umbrella/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index c61bae36e737..62bd72244636 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -549,7 +549,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-merkle-tree", "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api", "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-merkle-tree", "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api", "snowbridge-outbound-queue-runtime-api-v2", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", From 6e2dcfe03335379f23de6b5965bb6372520bc97f Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 7 Jan 2025 10:30:49 +0800 Subject: [PATCH 61/81] Fix taplo --- bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml index f3ef2a2f9430..b6377f2fd1ee 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue-v2/Cargo.toml @@ -82,7 +82,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", - "xcm/runtime-benchmarks" + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", From 63f9bec7be9a3dec9829baeb6c6115a53a18457d Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 7 Jan 2025 11:21:55 +0800 Subject: [PATCH 62/81] Fix taplo --- cumulus/bin/pov-validator/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index a919e3f68eac..d7af29a6bcb2 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -19,8 +19,8 @@ sc-executor.workspace = true sp-core.workspace = true sp-io.workspace = true sp-maybe-compressed-blob.workspace = true -tracing.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true [lints] workspace = true From af449b2730a82369dc6932e06c79c163f91f1a2b Mon Sep 17 00:00:00 2001 From: ron Date: Wed, 8 Jan 2025 00:10:22 +0800 Subject: [PATCH 63/81] Fix sol binding --- bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs index dfd449f83e74..9b9d5acd6dbc 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/envelope.rs @@ -11,7 +11,7 @@ use codec::Decode; use frame_support::pallet_prelude::{Encode, TypeInfo}; sol! { - event InboundMessageDispatched(uint64 indexed nonce, bool success, bytes32 indexed reward_address); + event InboundMessageDispatched(uint64 indexed nonce, bool success, bytes32 reward_address); } /// An inbound message that has had its outer envelope decoded. From bc7599cca07a7cbfa2152ebe8173eed315aa9ffe Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 16:54:49 +0800 Subject: [PATCH 64/81] Update WETH address --- Cargo.lock | 2 ++ bridges/snowbridge/pallets/system-frontend/Cargo.toml | 1 + bridges/snowbridge/pallets/system-v2/Cargo.toml | 1 + .../bridges/bridge-hub-westend/src/tests/snowbridge.rs | 2 +- .../asset-hub-westend/src/bridge_to_ethereum_config.rs | 8 ++++---- .../src/bridge_to_ethereum_config.rs | 10 +++------- cumulus/parachains/runtimes/constants/Cargo.toml | 3 +++ cumulus/parachains/runtimes/constants/src/westend.rs | 4 ++++ 8 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 756eb8385c86..a39a6267b1ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29357,9 +29357,11 @@ version = "1.0.0" dependencies = [ "cumulus-primitives-core 0.7.0", "frame-support 28.0.0", + "hex-literal", "polkadot-core-primitives 7.0.0", "rococo-runtime-constants 7.0.0", "smallvec", + "sp-core 28.0.0", "sp-runtime 31.0.1", "staging-xcm 7.0.0", "westend-runtime-constants 7.0.0", diff --git a/bridges/snowbridge/pallets/system-frontend/Cargo.toml b/bridges/snowbridge/pallets/system-frontend/Cargo.toml index bd1495a1489a..f6a0e569809c 100644 --- a/bridges/snowbridge/pallets/system-frontend/Cargo.toml +++ b/bridges/snowbridge/pallets/system-frontend/Cargo.toml @@ -64,6 +64,7 @@ runtime-benchmarks = [ "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/pallets/system-v2/Cargo.toml b/bridges/snowbridge/pallets/system-v2/Cargo.toml index 190b81f3723e..d7c07e738a6c 100644 --- a/bridges/snowbridge/pallets/system-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/system-v2/Cargo.toml @@ -73,6 +73,7 @@ runtime-benchmarks = [ "snowbridge-pallet-outbound-queue-v2/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 206005c9890c..fcfad93047d6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -31,7 +31,7 @@ use xcm_executor::traits::ConvertLocation; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; -pub const WETH: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); +pub const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index bc78f1cab52b..156bb18cff6c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -16,14 +16,14 @@ use crate::{xcm_config::AssetTransactors, Runtime, RuntimeEvent}; use frame_support::{parameter_types, traits::Everything}; -use hex_literal::hex; use pallet_xcm::EnsureXcm; -use xcm::prelude::{AccountKey20, Asset, Ethereum, GlobalConsensus, Location, SendXcm}; +use xcm::prelude::{AccountKey20, Asset, Location}; #[cfg(not(feature = "runtime-benchmarks"))] use crate::xcm_config::XcmRouter; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; +use testnet_parachains_constants::westend::snowbridge::{EthereumNetwork, WETHAddress}; #[cfg(feature = "runtime-benchmarks")] pub mod benchmark_helpers { @@ -58,10 +58,10 @@ parameter_types! { pub storage WETH: Location = Location::new( 2, [ - GlobalConsensus(Ethereum { chain_id: 11155111 }), + EthereumNetwork::get().into(), AccountKey20 { network: None, - key: hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), + key: WETHAddress::get().into(), }, ], ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index f04ea80eaba5..d41589b39dea 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -36,13 +36,14 @@ use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, + snowbridge::{ + AssetHubParaId, EthereumLocation, EthereumNetwork, WETHAddress, INBOUND_QUEUE_PALLET_INDEX, + }, }; use crate::xcm_config::RelayNetwork; #[cfg(feature = "runtime-benchmarks")] use benchmark_helpers::DoNothingRouter; -use cumulus_primitives_core::ParaId; use frame_support::{parameter_types, traits::Contains, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ @@ -62,11 +63,6 @@ pub type SnowbridgeExporter = EthereumBlobExporter< EthereumSystem, >; -parameter_types! { - pub storage WETHAddress: H160 = H160(hex_literal::hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")); - pub AssetHubParaId: ParaId = ParaId::from(westend_runtime_constants::system_parachain::ASSET_HUB_ID); -} - pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< UniversalLocation, EthereumNetwork, diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml index 01b023e0fb89..19ff1a9ffd60 100644 --- a/cumulus/parachains/runtimes/constants/Cargo.toml +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -15,10 +15,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +hex-literal = { workspace = true, default-features = true } smallvec = { workspace = true, default-features = true } # Substrate frame-support = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } # Polkadot @@ -37,6 +39,7 @@ std = [ "frame-support/std", "polkadot-core-primitives/std", "rococo-runtime-constants?/std", + "sp-core/std", "sp-runtime/std", "westend-runtime-constants?/std", "xcm/std", diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 8c4c0c594359..608183ce19ca 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -170,7 +170,9 @@ pub mod time { } pub mod snowbridge { + use cumulus_primitives_core::ParaId; use frame_support::parameter_types; + use sp_core::H160; use xcm::prelude::{Location, NetworkId}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. @@ -183,6 +185,8 @@ pub mod snowbridge { /// pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; pub EthereumLocation: Location = Location::new(2, EthereumNetwork::get()); + pub storage WETHAddress: H160 = H160(hex_literal::hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14")); + pub AssetHubParaId: ParaId = ParaId::from(westend_runtime_constants::system_parachain::ASSET_HUB_ID); } } From 6a333b72d57dbd47d106f735a8fe153be8efafe8 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 17:14:09 +0800 Subject: [PATCH 65/81] Update import --- .../bridge-hub-westend/tests/snowbridge.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index c1864c0e16d3..a0675e325546 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -20,12 +20,10 @@ use bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID; use bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID; use bp_polkadot_core::Signature; use bridge_hub_westend_runtime::{ - bridge_to_ethereum_config::{EthereumGatewayAddress, WETHAddress}, - bridge_to_rococo_config, - xcm_config::XcmConfig, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, - MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, TxExtension, - UncheckedExtrinsic, + bridge_to_ethereum_config::EthereumGatewayAddress, bridge_to_rococo_config, + xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, + Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, + TxExtension, UncheckedExtrinsic, }; use codec::{Decode, Encode}; use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; @@ -37,6 +35,7 @@ use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, }; +use testnet_parachains_constants::westend::snowbridge::WETHAddress; parameter_types! { pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; From 553ea2c4e8aa37b7d920d3aa8e02485f752b39e7 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 17:14:17 +0800 Subject: [PATCH 66/81] Cleanup --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index d5e0f1875da7..d48287657085 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,3 @@ substrate.code-workspace target/ *.scale justfile -python-venv From f2f805c4161201127914c20307d96c39d6947863 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 14 Jan 2025 18:20:12 +0800 Subject: [PATCH 67/81] Create agent with V2 system api --- Cargo.lock | 1 + .../snowbridge/pallets/system-v2/src/lib.rs | 11 +++++- .../snowbridge/pallets/system-v2/src/tests.rs | 8 +++- bridges/snowbridge/pallets/system/src/lib.rs | 34 ---------------- .../bridges/bridge-hub-westend/src/lib.rs | 1 + .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge_common.rs | 39 +++++++++---------- .../src/bridge_to_ethereum_config.rs | 4 +- 8 files changed, 39 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a39a6267b1ce..5b7231f024f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2883,6 +2883,7 @@ dependencies = [ "snowbridge-pallet-outbound-queue 0.2.0", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system 0.2.0", + "snowbridge-pallet-system-v2", "snowbridge-router-primitives 0.9.0", "sp-core 28.0.0", "sp-runtime 31.0.1", diff --git a/bridges/snowbridge/pallets/system-v2/src/lib.rs b/bridges/snowbridge/pallets/system-v2/src/lib.rs index 49c78e29a5c4..86159923235d 100644 --- a/bridges/snowbridge/pallets/system-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/system-v2/src/lib.rs @@ -65,6 +65,7 @@ where #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::dispatch::RawOrigin; #[pallet::pallet] pub struct Pallet(_); @@ -148,7 +149,10 @@ pub mod pallet { location: Box, fee: u128, ) -> DispatchResult { - T::SiblingOrigin::ensure_origin(origin)?; + let _ = match origin.clone().into() { + Ok(RawOrigin::Root) => Ok(Here.into()), + _ => T::SiblingOrigin::ensure_origin(origin), + }?; let origin_location: Location = (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; @@ -193,7 +197,10 @@ pub mod pallet { metadata: AssetMetadata, fee: u128, ) -> DispatchResult { - let origin_location = T::SiblingOrigin::ensure_origin(origin)?; + let origin_location = match origin.clone().into() { + Ok(RawOrigin::Root) => Ok(Here.into()), + _ => T::SiblingOrigin::ensure_origin(origin), + }?; let asset_location: Location = (*asset_id).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; diff --git a/bridges/snowbridge/pallets/system-v2/src/tests.rs b/bridges/snowbridge/pallets/system-v2/src/tests.rs index 69575f04f12a..81d128e5b945 100644 --- a/bridges/snowbridge/pallets/system-v2/src/tests.rs +++ b/bridges/snowbridge/pallets/system-v2/src/tests.rs @@ -10,7 +10,13 @@ fn create_agent() { let origin = make_xcm_origin(origin_location); let agent_origin = Location::new(1, [Parachain(2000)]); - let agent_id = make_agent_id(agent_origin.clone()); + + let reanchored_location = agent_origin + .clone() + .reanchored(&EthereumDestination::get(), &UniversalLocation::get()) + .unwrap(); + + let agent_id = make_agent_id(reanchored_location); assert!(!Agents::::contains_key(agent_id)); assert_ok!(EthereumSystem::create_agent( diff --git a/bridges/snowbridge/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs index c324459a8656..24575a75b14c 100644 --- a/bridges/snowbridge/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -637,40 +637,6 @@ pub mod pallet { pays_fee: Pays::No, }) } - - #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::create_agent())] - pub fn force_create_agent( - origin: OriginFor, - location: Box, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - let location: Location = - (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; - - let ethereum_location = T::EthereumLocation::get(); - let location = location - .clone() - .reanchored(ðereum_location, &T::UniversalLocation::get()) - .map_err(|_| Error::::LocationConversionFailed)?; - - let agent_id = agent_id_of::(&location)?; - - // Record the agent id or fail if it has already been created - ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); - Agents::::insert(agent_id, ()); - - let command = Command::CreateAgent { agent_id }; - let pays_fee = PaysFee::::No; - Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; - - Self::deposit_event(Event::::CreateAgent { location: Box::new(location), agent_id }); - Ok(PostDispatchInfo { - actual_weight: Some(T::WeightInfo::register_token()), - pays_fee: Pays::No, - }) - } } impl Pallet { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index b548e3b7e64c..0b0e23418fcd 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -47,6 +47,7 @@ decl_test_parachains! { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, + EthereumSystemV2: bridge_hub_westend_runtime::EthereumSystemV2, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index fdc2dc8bdce7..2339ce5ca8b5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -53,4 +53,5 @@ snowbridge-pallet-inbound-queue-fixtures = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-outbound-queue-v2 = { workspace = true } snowbridge-pallet-system = { workspace = true } +snowbridge-pallet-system-v2 = { workspace = true } snowbridge-router-primitives = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index 88d261cc105d..f7433ed6e735 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -350,15 +350,14 @@ pub fn register_ah_user_agent_on_ethereum() { ], ); - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); + assert_ok!(::EthereumSystemV2::create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + REMOTE_FEE_AMOUNT_IN_WETH, + )); assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + vec![RuntimeEvent::EthereumSystemV2(snowbridge_pallet_system_v2::Event::CreateAgent{ .. }) => {},] ); }); } @@ -370,15 +369,14 @@ pub fn register_penpal_agent_on_ethereum() { let location = Location::new(1, [Parachain(PenpalB::para_id().into())]); - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); + assert_ok!(::EthereumSystemV2::create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + REMOTE_FEE_AMOUNT_IN_WETH + )); assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + vec![RuntimeEvent::EthereumSystemV2(snowbridge_pallet_system_v2::Event::CreateAgent{ .. }) => {},] ); }); @@ -394,15 +392,14 @@ pub fn register_penpal_agent_on_ethereum() { ], ); - assert_ok!( - ::EthereumSystem::force_create_agent( - RuntimeOrigin::root(), - bx!(location.into()), - ) - ); + assert_ok!(::EthereumSystemV2::create_agent( + RuntimeOrigin::root(), + bx!(location.into()), + REMOTE_FEE_AMOUNT_IN_WETH, + )); assert_expected_events!( BridgeHubWestend, - vec![RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent{ .. }) => {},] + vec![RuntimeEvent::EthereumSystemV2(snowbridge_pallet_system_v2::Event::CreateAgent{ .. }) => {},] ); }); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index d41589b39dea..93a1b44f349a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -20,7 +20,7 @@ use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumOutboundQueueV2, EthereumSystem, - MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, + EthereumSystemV2, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; @@ -68,7 +68,7 @@ pub type SnowbridgeExporterV2 = EthereumBlobExporterV2< EthereumNetwork, snowbridge_pallet_outbound_queue_v2::Pallet, snowbridge_core::AgentIdOf, - EthereumSystem, + (EthereumSystem, EthereumSystemV2), WETHAddress, AssetHubParaId, >; From 3f3253f6b4fe35d4ad018785dbe7d02a8f22f3a5 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:15:14 +0800 Subject: [PATCH 68/81] Update bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 7762d3b71533..dbdce7d79d2f 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -135,7 +135,7 @@ pub mod pallet { #[pallet::constant] type GatewayAddress: Get; - /// Reward leger + /// Reward ledger type RewardLedger: RewardLedger<::AccountId, Self::Balance>; type ConvertAssetId: MaybeEquivalence; From ac6fd91d9b1395f77b060e0ca4c76464a568dd77 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:31:22 +0800 Subject: [PATCH 69/81] Update bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index dbdce7d79d2f..70a0adac33a3 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -154,7 +154,7 @@ pub mod pallet { message: Message, }, /// Message will be committed at the end of current block. From now on, to track the - /// progress the message, use the `nonce` of `id`. + /// progress the message, use the `nonce` or the `id`. MessageAccepted { /// ID of the message id: H256, From 80fcee6eaf05321f749723fbb38fab71a56bb092 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:39:45 +0800 Subject: [PATCH 70/81] Update bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 70a0adac33a3..3b412b6759e2 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -193,7 +193,7 @@ pub mod pallet { } /// Messages to be committed in the current block. This storage value is killed in - /// `on_initialize`, so should never go into block PoV. + /// `on_initialize`, so will not end up bloating state. /// /// Is never read in the runtime, only by offchain message relayers. /// From 2f5a8b2144a013ab1611d7fea27023ccaac67318 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:40:25 +0800 Subject: [PATCH 71/81] Update bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 3b412b6759e2..29e67be05e5c 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -196,6 +196,7 @@ pub mod pallet { /// `on_initialize`, so will not end up bloating state. /// /// Is never read in the runtime, only by offchain message relayers. + /// Because of this, it will never go into the PoV of a block. /// /// Inspired by the `frame_system::Pallet::Events` storage value #[pallet::storage] From 9f171ab5ad0aaa270fb7bc97c72c6e72d120db38 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:40:55 +0800 Subject: [PATCH 72/81] Update bridges/snowbridge/pallets/system-frontend/README.md Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/system-frontend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/README.md b/bridges/snowbridge/pallets/system-frontend/README.md index 28d9ffb9a4e7..392db04899a2 100644 --- a/bridges/snowbridge/pallets/system-frontend/README.md +++ b/bridges/snowbridge/pallets/system-frontend/README.md @@ -1,3 +1,3 @@ # Ethereum System Frontend -An frontend pallet deployed on AH for calling V2 system pallet on BH. +A frontend pallet deployed on AH for calling V2 system pallet on BH. From 6f1f5e6958d38d237745ade876b9711f7c6290e6 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:41:19 +0800 Subject: [PATCH 73/81] Update bridges/snowbridge/pallets/system-frontend/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index 72a2010e9b25..a87e3c89c440 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -90,7 +90,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// An CreateAgent message was sent to BH + /// A `CreateAgent` message was sent to Bridge Hub CreateAgent { location: Location }, /// Register Polkadot-native token was sent to BH RegisterToken { From 222c2827369fa674603cb235e10d445799c5abe6 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 09:41:38 +0800 Subject: [PATCH 74/81] Update bridges/snowbridge/pallets/system-frontend/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index a87e3c89c440..fcd1354faa0b 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -92,7 +92,7 @@ pub mod pallet { pub enum Event { /// A `CreateAgent` message was sent to Bridge Hub CreateAgent { location: Location }, - /// Register Polkadot-native token was sent to BH + /// A message to register a Polkadot-native token was sent to Bridge Hub RegisterToken { /// Location of Polkadot-native token location: Location, From 22e128863ba2a7c09f605fc795a226d26670eda0 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 30 Jan 2025 03:36:57 +0000 Subject: [PATCH 75/81] Comments --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index fcd1354faa0b..d8074e04fa65 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -101,9 +101,13 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// Convert versioned location failure UnsupportedLocationVersion, + /// Check location failure, should start from the dispatch origin as owner OwnerCheck, + /// Send xcm message failure Send, + /// Withdraw fee asset failure FundsUnavailable, } @@ -143,7 +147,7 @@ pub mod pallet { /// Registers a Polkadot-native token as a wrapped ERC20 token on Ethereum. /// - `origin`: Must be `Location` from a sibling parachain - /// - `asset_id`: Location of the asset (should be starts from the dispatch origin) + /// - `asset_id`: Location of the asset (should starts from the dispatch origin) /// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::register_token())] From 7925528cb9e1a3cac09c55cd1b07b323c49a8ba6 Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 11:38:56 +0800 Subject: [PATCH 76/81] Update bridges/snowbridge/pallets/system-frontend/src/lib.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/system-frontend/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/system-frontend/src/lib.rs b/bridges/snowbridge/pallets/system-frontend/src/lib.rs index d8074e04fa65..82be578deef3 100644 --- a/bridges/snowbridge/pallets/system-frontend/src/lib.rs +++ b/bridges/snowbridge/pallets/system-frontend/src/lib.rs @@ -113,7 +113,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Call create_agent on BH to instantiate a new agent contract representing `origin`. + /// Call `create_agent` on Bridge Hub to instantiate a new agent contract representing `origin`. /// - `origin`: Must be `Location` from a sibling parachain /// - `fee`: Fee in Ether #[pallet::call_index(1)] From 7d263d291d86eaf8d487edfe4072470f9684971c Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 11:39:59 +0800 Subject: [PATCH 77/81] Update bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs index 41bc0f2dd9a0..803831f42da7 100644 --- a/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs +++ b/bridges/snowbridge/primitives/outbound-router/src/v2/convert.rs @@ -114,7 +114,7 @@ where Ok(fee_amount) } - /// Convert the xcm for into the Message which will be executed + /// Convert the xcm into the Message which will be executed /// on Ethereum Gateway contract, we expect an input of the form: /// # WithdrawAsset(WETH) /// # PayFees(WETH) From 30d1c36b317e44e481cd5f36a89b1bae46a30d2a Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 30 Jan 2025 13:42:32 +0800 Subject: [PATCH 78/81] Rename to OutboundMessage --- .../outbound-queue-v2/runtime-api/src/lib.rs | 4 ++-- .../pallets/outbound-queue-v2/src/api.rs | 10 +++++----- .../pallets/outbound-queue-v2/src/lib.rs | 14 +++++++------- .../pallets/outbound-queue-v2/src/test.rs | 4 ++-- bridges/snowbridge/primitives/outbound/src/v2.rs | 8 ++++---- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs index a9b952e43557..adf53e07c727 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/runtime-api/src/lib.rs @@ -4,7 +4,7 @@ use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_merkle_tree::MerkleProof; -use snowbridge_outbound_primitives::{v2::InboundMessage, DryRunError}; +use snowbridge_outbound_primitives::{v2::OutboundMessage, DryRunError}; use xcm::prelude::Xcm; sp_api::decl_runtime_apis! { @@ -15,6 +15,6 @@ sp_api::decl_runtime_apis! { /// `sp_runtime::generic::DigestItem::Other` fn prove_message(leaf_index: u64) -> Option; - fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Balance),DryRunError>; + fn dry_run(xcm: Xcm<()>) -> Result<(OutboundMessage,Balance),DryRunError>; } } diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs index 9869632f7192..0ca1ba0827f0 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/api.rs @@ -6,7 +6,7 @@ use crate::{Config, MessageLeaves}; use frame_support::storage::StorageStreamIter; use snowbridge_merkle_tree::{merkle_proof, MerkleProof}; use snowbridge_outbound_primitives::{ - v2::{GasMeter, InboundCommandWrapper, InboundMessage, Message}, + v2::{GasMeter, Message, OutboundCommandWrapper, OutboundMessage}, DryRunError, }; use snowbridge_outbound_router_primitives::v2::convert::XcmConverter; @@ -26,7 +26,7 @@ where Some(proof) } -pub fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage, T::Balance), DryRunError> +pub fn dry_run(xcm: Xcm<()>) -> Result<(OutboundMessage, T::Balance), DryRunError> where T: Config, { @@ -37,17 +37,17 @@ where let fee = crate::Pallet::::calculate_local_fee(); - let commands: Vec = message + let commands: Vec = message .commands .into_iter() - .map(|command| InboundCommandWrapper { + .map(|command| OutboundCommandWrapper { kind: command.index(), gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), payload: command.abi_encode(), }) .collect(); - let message = InboundMessage { + let message = OutboundMessage { origin: message.origin, nonce: Default::default(), commands: commands.try_into().map_err(|_| DryRunError::ConvertXcmFailed)?, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs index 29e67be05e5c..f67e6fad4f76 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/lib.rs @@ -79,8 +79,8 @@ use snowbridge_core::{ }; use snowbridge_merkle_tree::merkle_root; use snowbridge_outbound_primitives::v2::{ - abi::{CommandWrapper, InboundMessageWrapper}, - GasMeter, InboundCommandWrapper, InboundMessage, Message, + abi::{CommandWrapper, OutboundMessageWrapper}, + GasMeter, Message, OutboundCommandWrapper, OutboundMessage, }; use sp_core::{H160, H256}; use sp_runtime::{ @@ -201,7 +201,7 @@ pub mod pallet { /// Inspired by the `frame_system::Pallet::Events` storage value #[pallet::storage] #[pallet::unbounded] - pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; + pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; /// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a /// merkle root during `on_finalize`. This storage value is killed in @@ -336,11 +336,11 @@ pub mod pallet { // b. Convert to committed hash and save into MessageLeaves // c. Save nonce&fee into PendingOrders let message: Message = Message::decode(&mut message).map_err(|_| Corrupt)?; - let commands: Vec = message + let commands: Vec = message .commands .clone() .into_iter() - .map(|command| InboundCommandWrapper { + .map(|command| OutboundCommandWrapper { kind: command.index(), gas: T::GasMeter::maximum_dispatch_gas_used_at_most(&command), payload: command.abi_encode(), @@ -356,7 +356,7 @@ pub mod pallet { payload: Bytes::from(command.payload), }) .collect(); - let committed_message = InboundMessageWrapper { + let committed_message = OutboundMessageWrapper { origin: FixedBytes::from(message.origin.as_fixed_bytes()), nonce, commands: abi_commands, @@ -365,7 +365,7 @@ pub mod pallet { ::Hashing::hash(&committed_message.abi_encode()); MessageLeaves::::append(message_abi_encoded_hash); - let inbound_message = InboundMessage { + let inbound_message = OutboundMessage { origin: message.origin, nonce, commands: commands.try_into().map_err(|_| Corrupt)?, diff --git a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs index f415d8f31e87..c7baeaec3470 100644 --- a/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/outbound-queue-v2/src/test.rs @@ -13,7 +13,7 @@ use frame_support::{ use codec::Encode; use snowbridge_core::{ChannelId, ParaId}; use snowbridge_outbound_primitives::{ - v2::{abi::InboundMessageWrapper, primary_governance_origin, Command, SendMessage}, + v2::{abi::OutboundMessageWrapper, primary_governance_origin, Command, SendMessage}, SendError, }; use sp_core::{hexdisplay::HexDisplay, H256}; @@ -262,7 +262,7 @@ fn encode_mock_message() { .collect(); // print the abi-encoded message and decode with solidity test - let committed_message = InboundMessageWrapper { + let committed_message = OutboundMessageWrapper { origin: FixedBytes::from(message.origin.as_fixed_bytes()), nonce: 1, commands, diff --git a/bridges/snowbridge/primitives/outbound/src/v2.rs b/bridges/snowbridge/primitives/outbound/src/v2.rs index ba5e49d47339..6f57a7653005 100644 --- a/bridges/snowbridge/primitives/outbound/src/v2.rs +++ b/bridges/snowbridge/primitives/outbound/src/v2.rs @@ -25,7 +25,7 @@ pub mod abi { use alloy_core::sol; sol! { - struct InboundMessageWrapper { + struct OutboundMessageWrapper { // origin bytes32 origin; // Message nonce @@ -101,20 +101,20 @@ pub mod abi { } #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] -pub struct InboundCommandWrapper { +pub struct OutboundCommandWrapper { pub kind: u8, pub gas: u64, pub payload: Vec, } #[derive(Encode, Decode, TypeInfo, PartialEq, Clone, RuntimeDebug)] -pub struct InboundMessage { +pub struct OutboundMessage { /// Origin pub origin: H256, /// Nonce pub nonce: u64, /// Commands - pub commands: BoundedVec>, + pub commands: BoundedVec>, } pub const MAX_COMMANDS: u32 = 8; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index fb25fc26cc65..bd61a0b6abdd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -101,7 +101,7 @@ use parachains_common::{ use snowbridge_core::{AgentId, PricingParameters}; use snowbridge_outbound_primitives::{ v1::{Command, Fee}, - v2::InboundMessage, + v2::OutboundMessage, DryRunError, }; use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; @@ -919,7 +919,7 @@ impl_runtime_apis! { fn prove_message(leaf_index: u64) -> Option { snowbridge_pallet_outbound_queue_v2::api::prove_message::(leaf_index) } - fn dry_run(xcm: Xcm<()>) -> Result<(InboundMessage,Balance),DryRunError> { + fn dry_run(xcm: Xcm<()>) -> Result<(OutboundMessage,Balance),DryRunError> { snowbridge_pallet_outbound_queue_v2::api::dry_run::(xcm) } } From 82ab0eee2b3c8331d75be8403e59d6f978b4d223 Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 30 Jan 2025 15:46:02 +0800 Subject: [PATCH 79/81] Fix tests --- Cargo.toml | 1 - .../bridge-hub-westend/src/tests/snowbridge_common.rs | 8 +++----- .../src/tests/snowbridge_v2_outbound.rs | 9 ++------- .../asset-hub-westend/src/bridge_to_ethereum_config.rs | 4 ++-- .../runtimes/assets/asset-hub-westend/src/lib.rs | 4 ++-- umbrella/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bfdfef461de..d1c42dd281d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1252,7 +1252,6 @@ snowbridge-pallet-system-v2 = { path = "bridges/snowbridge/pallets/system-v2", d snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } snowbridge-runtime-common = { path = "bridges/snowbridge/runtime/runtime-common", default-features = false } snowbridge-runtime-test-common = { path = "bridges/snowbridge/runtime/test-common", default-features = false } -snowbridge-system-frontend = { path = "bridges/snowbridge/pallets/system-frontend", default-features = false } snowbridge-system-runtime-api = { path = "bridges/snowbridge/pallets/system/runtime-api", default-features = false } snowbridge-system-runtime-api-v2 = { path = "bridges/snowbridge/pallets/system-v2/runtime-api", default-features = false } soketto = { version = "0.8.0" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs index f7433ed6e735..8ffb5f03f723 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_common.rs @@ -13,11 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - create_pool_with_native_on, - imports::*, - tests::snowbridge::{CHAIN_ID, WETH}, -}; +use crate::{create_pool_with_native_on, imports::*}; use asset_hub_westend_runtime::xcm_config::LocationToAccountId; use emulated_integration_tests_common::PenpalBTeleportableAssetLocation; use frame_support::traits::fungibles::Mutate; @@ -33,6 +29,8 @@ use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; +pub const CHAIN_ID: u64 = 11155111; +pub const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); pub const INITIAL_FUND: u128 = 50_000_000_000_000; pub const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); pub const AGENT_ADDRESS: [u8; 20] = hex!("90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe"); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs index 9465f09d6985..a9a3a9b55d15 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2_outbound.rs @@ -13,17 +13,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - imports::*, - tests::{snowbridge::WETH, snowbridge_common::*}, -}; +use crate::{imports::*, tests::snowbridge_common::*}; use emulated_integration_tests_common::{impls::Decode, PenpalBTeleportableAssetLocation}; use frame_support::pallet_prelude::TypeInfo; use rococo_westend_system_emulated_network::penpal_emulated_chain::penpal_runtime::xcm_config::LocalTeleportableToAssetHub; use snowbridge_core::AssetMetadata; use snowbridge_outbound_primitives::TransactInfo; use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; -use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm::v5::AssetTransferFilter; use xcm_executor::traits::ConvertLocation; @@ -244,8 +240,7 @@ fn send_weth_and_dot_from_asset_hub_to_ethereum() { #[test] fn transact_with_agent() { - let weth_asset_location: Location = - (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + let weth_asset_location: Location = weth_location(); fund_on_bh(); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs index 156bb18cff6c..bfd737f99c8d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/bridge_to_ethereum_config.rs @@ -47,7 +47,7 @@ pub mod benchmark_helpers { } } - impl snowbridge_system_frontend::BenchmarkHelper for () { + impl snowbridge_pallet_system_frontend::BenchmarkHelper for () { fn make_xcm_origin(location: Location) -> RuntimeOrigin { RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) } @@ -68,7 +68,7 @@ parameter_types! { pub storage DeliveryFee: Asset = (Location::parent(), 80_000_000_000u128).into(); } -impl snowbridge_system_frontend::Config for Runtime { +impl snowbridge_pallet_system_frontend::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 495faa1e1b0c..bc58a2450b5d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1153,7 +1153,7 @@ construct_runtime!( StateTrieMigration: pallet_state_trie_migration = 70, // Snowbridge - SnowbridgeSystemFrontend: snowbridge_system_frontend = 80, + SnowbridgeSystemFrontend: snowbridge_pallet_system_frontend = 80, // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. @@ -1454,8 +1454,8 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] - [snowbridge_system_frontend, SnowbridgeSystemFrontend] [cumulus_pallet_weight_reclaim, WeightReclaim] + [snowbridge_pallet_system_frontend, SnowbridgeSystemFrontend] ); } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 9455a040584b..fa810c601673 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -558,7 +558,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-merkle-tree", "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-pallet-system-v2", "snowbridge-pallet-system-frontend", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-merkle-tree", "snowbridge-outbound-primitives", "snowbridge-outbound-queue-runtime-api", "snowbridge-outbound-router-primitives", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-outbound-queue-v2", "snowbridge-pallet-system", "snowbridge-pallet-system-frontend", "snowbridge-pallet-system-v2", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", From ce6ce359c89182a86afd9a3ea41db9bb4d6899db Mon Sep 17 00:00:00 2001 From: Ron Date: Thu, 30 Jan 2025 17:26:17 +0800 Subject: [PATCH 80/81] Update bridges/snowbridge/pallets/system-v2/README.md Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/system-v2/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bridges/snowbridge/pallets/system-v2/README.md b/bridges/snowbridge/pallets/system-v2/README.md index 5ab11d45eae2..4c2811c58628 100644 --- a/bridges/snowbridge/pallets/system-v2/README.md +++ b/bridges/snowbridge/pallets/system-v2/README.md @@ -1,3 +1,4 @@ # Ethereum System +Pallet deployed on Bridge Hub. Contains management functions to manage functions on Ethereum. For example, creating agents and channels. From c96a1b27866d67bb9dfb681d9923702aa6cf394d Mon Sep 17 00:00:00 2001 From: ron Date: Thu, 30 Jan 2025 21:31:08 +0800 Subject: [PATCH 81/81] Fix clippy --- bridges/snowbridge/pallets/system-v2/src/mock.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/system-v2/src/mock.rs b/bridges/snowbridge/pallets/system-v2/src/mock.rs index be2390bb8539..047068f278a4 100644 --- a/bridges/snowbridge/pallets/system-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/system-v2/src/mock.rs @@ -175,12 +175,7 @@ pub struct AllowFromAssetHub; impl Contains for AllowFromAssetHub { fn contains(location: &Location) -> bool { match location.unpack() { - (1, [Parachain(para_id)]) => - if *para_id == 1000 { - true - } else { - false - }, + (1, [Parachain(para_id)]) => *para_id == 1000, _ => false, } }